在typescript中,“this”类型如何真正推断其类型?

z0qdvdin  于 2023-04-07  发布在  TypeScript
关注(0)|答案(1)|浏览(203)

在typescript中,我想使用this键来键入我的类的一些属性。然而,我面临着一个我无法解决的问题。我想做的是:

export class Animal{
/*some properties*/
}

export class Population{
    AnimalConstructor : typeof Animal = Animal;
    animal : InstanceType<this['AnimalConstructor']>;

    createAnimal(){
        this.animal = new this.AnimalConstructor();//Type 'Animal' is not assignable to type 'InstanceType<this["AnimalConstructor"]>'
    }
}

这样做,我有错误:Type 'Animal' is not assignable to type 'InstanceType<this["AnimalConstructor"]>'在第10行。然而,这段代码运行良好:

export class Animal{
/*some properties*/
}

export class Population{
    AnimalConstructor : typeof Animal = Animal;
    animal : Animal;

    createAnimal(){
        this.animal = new this.AnimalConstructor();
    }

}

我不明白为什么最后一个可以工作,而第一个不行。这可能是由于编译器推断this关键字类型的方式。但我也没有找到任何文档能够解释这种行为。官方文档只说:'一个叫做this的特殊类型动态引用当前类的类型。' -这并不能解释为什么上面的例子不起作用。

uyto3xhc

uyto3xhc1#

多态this类型充当一个隐式generic类型参数,它被约束到当前类类型。但只有在访问类或其子类的特定示例时才指定为类型参数。(有关将this描述为隐式类型参数的实现拉取请求,请参见microsoft/TypeScript#4910。)这意味着当您使用this类型时,您可以获得泛型的优点和缺点。
InstanceType<T>实用程序类型被实现为conditional type,正如您从其定义中所看到的:

type InstanceType<T extends abstract new (...args: any) => any> = 
  T extends abstract new (...args: any) => infer R ? R : any;

因此,在Population类定义的主体中,类型InstanceType<this['AnimalConstructor']>是一个 * 泛型条件类型 *(一个依赖于至少一个尚未指定的类型参数的条件类型)。不幸的是,编译器无法真正推理出这样的类型。
当计算值new this.AnimalConstructor()时,编译器将this的表观类型扩展为Animal,因为您正在访问泛型类型值的特定属性,编译器将这种扩展作为一种近似,使事情变得更容易。(参见microsoft/TypeScript#33181上的注解以获取源代码。)因此this.AnimalConstructor被视为类型typeof Animal,因此new this.AnimalConstructor()被视为类型Animal

const ac = this.AnimalConstructor;
//const ac: typeof Animal
const a = new ac();
//const a: Animal;

但是编译器 * 推迟 * 对InstanceType<this["AnimalConstructor"]>等通用条件类型的求值,因此最终将此类类型视为本质上 * 不透明 * 的类型。如果您尝试为此类类型的变量赋值,编译器几乎肯定会抱怨,因为它无法验证值是否与类型兼容。作为一个人,你可以检查值is并理解条件类型背后的含义,然后说“是的,这很好”,但是编译器通常将类型视为一个黑盒,并且不知道什么可能与它兼容。(与此问题最接近的文档是microsoft/TypeScript#33912)。
你得到一个错误:

this.animal = a; // error!
// Type 'Animal' is not assignable to 
// type 'InstanceType<this["AnimalConstructor"]>' 😟

如果你想保持你的类型不变,那么最好的方法可能就是接受你比编译器更聪明的事实。既然你确定new this.AnimalConstructor()的类型是InstanceType<this["AnimalConstructor"]>,不管this在子类中是什么,那么你可以直接向编译器Assert这个事实,以避免它担心它无法解决的问题:

createAnimal() {
  const ac = this.AnimalConstructor;    
  const a = new ac();
  this.animal = a as InstanceType<this['AnimalConstructor']>; // okay
}

或者只是

createAnimal() {
  this.animal = new this.AnimalConstructor() as typeof this.animal; // okay
}

如果编译器更聪明的话,它可能不像类型安全的那样,但这可能是你在不重构的情况下所能做的最好的事情......例如,在AnimalConstructor的示例类型中使Population * 显式 * 泛型,这样你就可以控制泛型何时扩大,并完全避免条件类型:

export class Population<T extends Animal> {
  constructor(public AnimalConstructor: new () => T) {
    this.animal = new AnimalConstructor(); // things should be initialized
  }
  animal: T

  createAnimal() {
    this.animal = new this.AnimalConstructor(); // okay
  }
}

Playground链接到代码

相关问题