在 TypeScript 中,’this’ 类型是如何推断其类型的?

huangapple go评论65阅读模式
英文:

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

问题

在TypeScript中,我想要使用this关键字来定义我的类的一些属性。然而,我遇到了一个无法解决的问题。

我想要做的事情类似于这样:

export class Animal{
/*一些属性*/
}

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

    createAnimal(){
        this.animal = new this.AnimalConstructor(); //类型'Animal'无法赋值给类型'InstanceType<this["AnimalConstructor"]>'
    }
}

通过这样做,我在第10行遇到错误:类型'Animal'无法赋值给类型'InstanceType<this["AnimalConstructor"]>'
然而,以下代码可以正常工作:

export class Animal{
/*一些属性*/
}

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

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

我无法理解为什么后者能够工作而前者不能。这可能是由于编译器推断this关键字的类型方式不同。但我找不到任何能解释这种行为的文档。官方文档只说:'一个特殊的类型称为this,动态地引用当前类的类型。' - 这并不能解释上面的示例为什么不起作用。

英文:

In typescript, I would like to use the this keywork to type some properties of my class. However, I face a problem I can't solve.
What I would like to do is something like :

export class Animal{
/*some properties*/
}

export class Population{
    AnimalConstructor : typeof Animal = Animal;
    animal : InstanceType&lt;this[&#39;AnimalConstructor&#39;]&gt;;

    createAnimal(){
        this.animal = new this.AnimalConstructor();//Type &#39;Animal&#39; is not assignable to type &#39;InstanceType&lt;this[&quot;AnimalConstructor&quot;]&gt;&#39;
    }
}

Doing that, I have the error : Type &#39;Animal&#39; is not assignable to type &#39;InstanceType&lt;this[&quot;AnimalConstructor&quot;]&gt;&#39; on line 10.
However, this code works well:

export class Animal{
/*some properties*/
}

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

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

}

I can't understand why the last one work and the first one doesn't. This is probably due to the way the compiler infers the type of the this keyword. But I didn't find any docs able to explain that behaviour neither. The official document only says : 'a special type called this refers dynamically to the type of the current class.' - which doesn't explain why the example above doesn't work.

答案1

得分: 2

以下是代码部分的翻译:

英文:

The polymorphic this type acts as an implicit generic type parameter which is constrained to the current class type, but is only specified as a type argument when you access a specific instance of the class or its subclass. (See microsoft/TypeScript#4910 for the implementing pull request that describes this as an implicit type parameter.) That means when you use the this type you get the benefits—and the drawbacks—of generics.

The InstanceType&lt;T&gt; utility type is implemented as a conditional type, as you can see from its definition:

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

So, inside the body of the Population class definition, the type InstanceType&lt;this[&#39;AnimalConstructor&#39;]&gt; is a generic conditional type (a conditional type that depends on at least one as-yet-unspecified type parameter). And, unfortunately, the compiler cannot really reason about such types.


When evaluating the value new this.AnimalConstructor(), the compiler widens the apparent type of this to Animal, because you're accessing a specific property on a generic-typed value, and the compiler does this widening as an approximation to make things easier. (See this comment on microsoft/TypeScript#33181 for a source.) And so this.AnimalConstructor is seen as type typeof Animal and thus new this.AnimalConstructor() is seen as type Animal:

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

But the compiler defers evaluation of generic conditional types like InstanceType&lt;this[&quot;AnimalConstructor&quot;]&gt;, and therefore ends up treating such types as essentially opaque. If you try to assign a value to a variable of such a type, the compiler will almost certainly complain because it can't verify that the value is compatible with the type. As a human being you can examine the value is and understand the meaning behind the conditional type and say "yes this is fine", but the compiler mostly sees the type as a black box and it has no idea what might be compatible with it. (The closest thing to documentation for this issue is microsoft/TypeScript#33912).

So you get an error:

this.animal = a; // error!
// Type &#39;Animal&#39; is not assignable to 
// type &#39;InstanceType&lt;this[&quot;AnimalConstructor&quot;]&gt;&#39; &#128543;

If you want to keep your types as they are, then probably the best way forward is just to accept that you're smarter than the compiler. Since you're sure that new this.AnimalConstructor() is clearly of type InstanceType&lt;this[&quot;AnimalConstructor&quot;]&gt; no matter what this turns out to be in subclasses, then you can just assert that fact to the compiler to stop it from worrying about things it can't figure out:

createAnimal() {
  const ac = this.AnimalConstructor;    
  const a = new ac();
  this.animal = a as InstanceType&lt;this[&#39;AnimalConstructor&#39;]&gt;; // okay
}

or just

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

That works and you can move on. It's not as type safe as it could be if the compiler were smarter, but it's the probably best you can do without refactoring... for example, to make Population explicitly generic in the instance type of AnimalConstructor so you can control when generics get widened and avoid conditional types altogether:

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

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

Playground link to code

huangapple
  • 本文由 发表于 2023年4月6日 19:51:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75949199.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定