英文:
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<this['AnimalConstructor']>;
createAnimal(){
this.animal = new this.AnimalConstructor();//Type 'Animal' is not assignable to type 'InstanceType<this["AnimalConstructor"]>'
}
}
Doing that, I have the error : Type 'Animal' is not assignable to type 'InstanceType<this["AnimalConstructor"]>'
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]" 翻译为 "多态的this
类型" -
"(See [microsoft/TypeScript#4910]" 翻译为 "(查看 [microsoft/TypeScript#4910]"
-
"The [
InstanceType<T>
utility type]" 翻译为 " [InstanceType<T>
实用类型]" -
"So, inside the body of the
Population
class definition," 翻译为 "因此,在Population
类定义的内部" -
"And, unfortunately, the compiler cannot really reason about such types." 翻译为 "不幸的是,编译器无法真正理解这种类型。"
-
"When evaluating the value
new this.AnimalConstructor()
, the compiler widens the apparent type ofthis
toAnimal
" 翻译为 "当评估值new this.AnimalConstructor()
时,编译器将this
的明显类型扩展为Animal
" -
"But the compiler defers evaluation of generic conditional types like
InstanceType<this["AnimalConstructor"]>
" 翻译为 "但编译器推迟对InstanceType<this["AnimalConstructor"]>
等通用条件类型的评估。" -
"So you get an error:" 翻译为 "因此,您会收到一个错误:"
-
"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." 翻译为 "如果您想保持类型不变,那么可能最好的方法就是接受您比编译器聪明。"
-
"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..." 翻译为 "如果编译器更聪明,它的类型安全性可能不如它本来可以的那样,但在不进行重构的情况下,这可能是您最好的选择。"
-
"Playground link to code" 翻译为 "[代码的 Playground 链接](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBAYwDYEMDOa4EEB2BLAWxSTgG8AoOOAegCo0IDg4woIxhY9g1bryAvuXKhIsRKgxwACuwCuqGHgg4ylbPiJIAwirQwochDGgAuODACeHCADMNhYnAC8DrQG51KTcQCE5gEkcfW8EYAAVa2AAHhgACzw0AG0AclxHHT0DIxMoFIBdAD53Gmo4HAh4PHwlYjwAL2qAczhbaDgGJjgoYHQVOAC4ABMVFPg5HCHOEMmAGgsEzAQUOTQeOE42KExAXg3AIj3hKgQelBhgdK0ACgBKNSojrLgUBBcFxIA6C+JdYOzjaE891KCEez3MVhs9i+SHUD1+T1eOGAAHcnggboD7tRqCD4ShzNDMVR4h9vBlXigStiNlAtr5YaU4JEOHA0j4kCk4IlypUnhg8E0cCgAEZIZgmOAM6kQ5gpIIzMLMmIk5IAImhP30hn+UFVRU5O0A+HsMlXvMlaCl8ga-UIRKKxRapDVZbW5ArFRkQADWKEs6iE6mOvTO0IATDc7vdTeanK4kajTc7fq7oBH0BYonY3mgzeyqWVvb7-YJhCJwNB4Mh0JhZGAFKdlDhQ9FwhsQGdJphoYVI7itTlU
英文:
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<T>
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<this['AnimalConstructor']>
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<this["AnimalConstructor"]>
, 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 'Animal' is not assignable to
// type 'InstanceType<this["AnimalConstructor"]>' 😟
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<this["AnimalConstructor"]>
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<this['AnimalConstructor']>; // 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<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
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论