在打字稿中,'this'类型如何真正推断其类型?



在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的特殊类型动态引用当前类的类型。’——这并不能解释为什么上面的例子不起作用。

多态this类型充当隐式泛型类型参数,约束于当前类类型,但仅在访问类或其子类的特定实例时指定为类型参数。(参见microsoft/TypeScript#4910实现将this描述为隐式类型参数的pull request。)这意味着当您使用this类型时,您将获得泛型的优点和缺点。

InstanceType<T>实用程序类型是作为条件类型实现的,正如您可以从其定义中看到的:

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在子类中是什么,那么您可以向编译器断言这一事实,以阻止它担心它无法弄清楚的事情:

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
}

这行得通,你可以继续了。如果编译器更聪明的话,它可能不是类型安全的,但这可能是你在不重构的情况下所能做的最好的事情……例如,要使PopulationAnimalConstructor的实例类型中显式地泛型,这样您就可以控制何时扩展泛型并完全避免条件类型:

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链接到代码

最新更新