特定类型X
与Ageneric类型参数T
、constrained至X
不同,如T extends X
.您对T
所做的任何操作都需要适用于可赋值给X
的every possible type.例如,如果X
是{a: string}
,则您对T extends X
所做的任何操作都需要适用于X
的子类型,如{a: string, b: number}
或{a: "x" | "y"}
.如果你有一个X
类型的变量x
,你可以写x.a = "hello"
,但如果你有一个T extends X
类型的变量t
,你cannot写t.a = "hello"
,因为如果T
变成{a: "x" | "y"}
,这就不起作用了.
在您的示例中,特定类型X
是Base
,定义为{[k: string]: any}
,字符串index signature的属性是the any
type.这是一种非常松散的类型:它本质上意味着可以具有任何键和任何值的属性的类型.所以如果arg
是Base
类型,那么你可以赋值arg.newProp = 8
.
但T extends Base
是不同的;您所做的任何事情都需要对Base
的每个可能的子类型有效.由于Base
是如此松散,所以Base
有许多相互不兼容的子类型可供 Select .例如,{a: string}
是Base
的子类型:
const v = { a: "hello" };
new Class(v); // okay
v.newProp = 8; // error, Property 'newProp' does not exist
而且该类型不知道有一个newProp
键,所以当您试图将8
赋给它的newProp
属性时,编译器会报错.这样的分配可能是无害的,但TS不会让你分配给它不确定是否存在的键.103 can lack an index signature when 104 has one有点奇怪,但这就是为什么您会收到错误消息的原因.
此外,{newProp: string}
是Base
的一个子类型:
const n = { newProp: "str" };
new Class(n);
n.newProp = 8; // error, number not assignable to string
对于您的实现来说,这无疑是一个更糟糕的问题.如果您传入的值已经有一个newProp
属性,并且8
不可赋值给它,那么您的类实现将做一些不受欢迎的事情.
注意:这些问题也可能在特定类型中出现:new Class(v)
和new Class(n)
行仍然使用Class
的非泛型版本进行编译,导致与上面相同的令人不快的后果.Typescript 的排字系统是intentionally unsound in places.因此,您使用泛型所做的操作并不是"错误的",而另一个版本则是"正确的".
只是类型脚本假定您使用的是泛型,因为您需要这种额外的全能子类型判断.相反,如果您不想进行这种判断,则应该使用特定类型而不是泛型类型.或者,如果您确实使用泛型,那么您将需要使用type assertions或类似的方法来根据您的喜好放松类型判断.
Playground link to code个