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

In typescript, how does the 'this' type really infer its type?

提问人:Apochotolasys 提问时间:4/6/2023 最后编辑:Apochotolasys 更新时间:4/6/2023 访问量:67

问:

在打字稿中,我想使用 keywork 来键入类的一些属性。但是,我面临着一个我无法解决的问题。 我想做的是这样的: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"]>'
    }
}

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

export class Animal{
/*some properties*/
}

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

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

}

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

TypeScript 类型 如下

评论

2赞 jcalz 4/6/2023
多态类型在子类中变窄,因此,如果您正在执行的操作不适用于所有可能的子类,则不应使用 .考虑一下这个演示,如果你用 ;该属性的类型为 ,因此未正确实现。如果你改成,那么 的属性就像我想你想要的一样。这是否完全解决了这个问题?如果是这样,我会写一个答案;如果没有,我错过了什么?thisthisPopulationDogPopulationanimalDogcreateAnimal()thisPopulationanimalDogPopulationAnimal
0赞 Apochotolasys 4/6/2023
感谢您的回答!确实,你是对的;我的问题模棱两可,过于简单化。我所做的不是:但是,如果我没有弄错,在您的链接示例中,一切都应该正确工作?我用所需的行更新了问题this.animal = new Animal();this.animal = new this.AnimalConstructor();
1赞 jcalz 4/6/2023
该类型是隐式泛型类型参数,并且是作为条件类型实现的,因此是泛型条件类型...编译器真的无法推理泛型条件类型(这本质上与 ms/TS#33912 中描述的问题相同),所以它抱怨。在新代码中,唯一合理的方法(使用现有类型)是使用类型断言,如下所示这是否完全解决了这个问题?如果是这样,✍;如果没有, ❓thisInstanceTypeInstanceType<this["xxx"]>
0赞 Apochotolasys 4/6/2023
它可以是答案;但是,我认为您的链接中的问题对于编译器来说比这个更复杂?例如,我刚刚注意到编译器接受了这种语法:然后将 的类型正确推断为您是否有关于编译器在这种情况下如何工作的文档?this.animal = new (this.AnimalConstructor.bind(this.AnimalConstructor))();this.animalAnimal
1赞 Apochotolasys 4/6/2023
是的,当然,如果链接是我们能得到的最好的文档,那就足够了!感谢您提供的信息和您的时间!

答:

2赞 jcalz 4/6/2023 #1

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

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

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

因此,在类定义的主体中,类型是泛型条件类型(依赖于至少一个尚未指定的类型参数的条件类型)。而且,不幸的是,编译器无法真正推理这些类型。PopulationInstanceType<this['AnimalConstructor']>


在计算值时,编译器会将表观类型 to 加宽,因为您正在访问泛型类型化值的特定属性,并且编译器将此扩展作为近似值进行,以使事情变得更容易。(有关来源,请参阅 microsoft/TypeScript#33181 上的此评论。所以被看作是类型,因此被看作是类型:new this.AnimalConstructor()thisAnimalthis.AnimalConstructortypeof Animalnew this.AnimalConstructor()Animal

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

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

所以你得到一个错误:

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

评论

0赞 Apochotolasys 4/7/2023
谢谢,这很清楚,链接有助于理解!