在处理generic个类型的值的操作时,编译器需要进行权衡.要么操作的结果可以保持泛型,这可能会产生非常精确和准确的类型,但这种复杂的泛型类型对于编译器来说很难分析;或者泛型类型可以首先扩大到其constraint,以便结果类型是特定的,这更容易分析,但可能会导致精度损失.编译器使用启发式规则来确定何时传播泛型以及何时扩大到具体.
例如,在内部
let get = <T extends { age: number, name: string }>(x: T) => {
const age = x.age // what type is age?
return age;
}
age
应该是什么类型的?由于x
是T
类型,并且您使用类型"age"
的键对其进行索引,因此x
的精确泛型类型是indexed access type T["age"]
.另一方面,我们知道x
的类型是{age: number, name: string}
的子类型.所以我们可以将x
扩大到那个类型,在这种情况下,x.age
的类型是{age: number, name: string}["age"]
,也就是number
.因此,这里有两种明显的可能性:age
要么保持泛型,属于T["age"]
类型,要么被扩展为更直接可用的特定类型number
.
编译器是做什么的?
let get = <T extends { age: number, name: string }>(x: T): T[keyof T] => {
const age = x.age;
// const age: number
return age; // error! Type 'number' is not assignable to type 'T[keyof T]'.
}
它被扩大到number
.这种行为在this comment/microsoft/TypeScript#33181中有记录,与您现在看到的问题类似.稍微转述一下:
属性访问和元素访问返回约束的对应属性类型,因此[x.age
具有类型number
],这就是[return
语句]失败的原因.一旦你退回到一些具体的东西,你以后就不能用一般的东西来索引.
也就是说,当您返回age
时,编译器将看到您返回了一个类型为number
的值.不幸的是,number
不一定可以赋值给T[keyof T]
,如下所示:
interface Centenarian {
name: string,
age: 100,
}
declare const oldManJenkins: Centenarian;
const g = get(oldManJenkins);
// const g: string | 100
A Centenarian
的age
总是literal type 100
,比number
窄.在get()
中,编译器将age
从"不管T["age"]
变成什么"扩展到number
,并且number
不能赋给string | 100
(因为,比方说,99.5
是number
,但不是string | 100
).
这就是你得到错误的原因.
至于如何处理它,你可以做一些类似于微软/TypeScrip#33181中所示的事情.显式地对具有所需泛型类型的age
变量进行annotate,因此编译器会提示不要将其扩大:
let get = <T extends { age: number, name: string }>(x: T): T[keyof T] => {
const age: T['age'] = x.age; // okay
return age; // okay
}
现在可以看到age
是T['age']
类型的,可以将其赋值给T[keyof T]
,并且该函数编译时没有错误.
Playground link to code个