TL;DR: 重载 https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads哪个是generic https://www.typescriptlang.org/docs/handbook/2/generics.html没有正确进行类型检查,如中所述微软/TypeScript#26631 https://github.com/microsoft/TypeScript/issues/26631 and 微软/TypeScript#50050 https://github.com/microsoft/TypeScript/issues/50050.
Your IFoo
接口声称是采用一个类型参数的泛型类构造函数的类型T
但没有实际值参数,并返回类型的类实例Foo<T>
这或多或少与T
(因为这是身份映射类型 https://www.typescriptlang.org/docs/handbook/2/mapped-types.html复制所有非签名属性)。所以我有一个价值Foo
类型的IFoo
,那么我大概可以这样写:
declare const Foo: IFoo;
const withStringA = new Foo<{a: string}>();
withStringA.a.toUpperCase();
// const withStringA: Foo<{ a: string; }>
const withNumberA = new Foo<{a: number}>();
// const withNumberA: Foo<{ a: number; }>
withNumberA.a.toFixed();
它将作为以下 JavaScript 发出:
const withStringA = new Foo();
withStringA.a.toUpperCase();
const withNumberA = new Foo();
withNumberA.a.toFixed();
但两者withStringA
and withNumberA
使用完全相同的构造调用进行初始化,new Foo()
。怎么能withStringA.a
属于类型string
but withNumberA.a
属于类型number
?没有任何合理的机制可以让这种事情发生。没有魔法,IFoo
is 无法实现的,至少不安全。
尤其,class {}
没有正确实施IFoo
。它根本没有属性,更不用说a
神奇的财产string
or number
取决于运行时不可用的信息。如果您尝试运行它,您将收到运行时错误:
const Foo: IFoo = class { };
const withStringA = new Foo<{ a: string }>();
// const withStringA: Foo<{ a: string; }>
withStringA.a.toUpperCase(); // RUNTIME ERROR ???? x.a is undefined
So if IFoo
无法安全实施,why编译器没有警告你吗?
事实上,正如您所注意到的,您do当您删除第二个时收到警告构建签名 https://www.typescriptlang.org/docs/handbook/2/functions.html#construct-signatures:
interface IFoo {
new <T>(): Foo<T>;
}
const Foo: IFoo = class { }; // error!
// -> ~~~
// Type 'typeof Foo' is not assignable to type 'IFoo'.
这个错误是expected and good.
但一旦你添加第二个,超载 https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads构造签名,错误消失:
interface IFoo {
new <T>(): Foo<T>;
new <T>(): Foo<T>;
}
const Foo: IFoo = class { }; // okay?!
Why?
问题是generic https://www.typescriptlang.org/docs/handbook/2/generics.html重载没有正确进行类型检查,如中所述微软/TypeScript#26631 https://github.com/microsoft/TypeScript/issues/26631 and 微软/TypeScript#50050 https://github.com/microsoft/TypeScript/issues/50050。类型参数替换为不安全的any type https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any,这意味着编译器检查分配class {}
as if IFoo
were:
interface IFooLike {
new(): Foo<any>;
new(): Foo<any>;
}
这与以下相同
interface IFooLike {
new(): any;
new(): any;
}
确实,class {}
确实匹配该类型:
const Foo: IFooLike = class { };
所以不存在编译器错误,尽管应该有。
这实际上是 TypeScript 的设计限制;这是一个错误,但如果他们修复它,许多当前有效(并且恰好是安全的)的代码可能会开始发出编译器警告(因为编译器无法很好地验证重载安全性)。
因此,如果您确实决定对某个类型使用多个泛型调用或构造签名,请小心实现它的方式,因为编译器可能会默默地无法捕获错误。
Playground 代码链接 https://www.typescriptlang.org/play?#code/LAKAdghgtgpgzgBwgYxgAgPIDcA2ATNAb1DTQBcBPBdAMQHs6AeAFQD40BeIk0tAbQDKaAJZg0AaxgU6AMzTMAugC55ghTwC+oHqLIwATjJToAkvTrcQvNGBgB3NC1YAKAJQrzTgNw9Sth05uHgzeaAD0YWgA0hgicvowUHRYMHia2lbk+hSW1sh0YHBkaOYqZgycaMg4EHBwRGgaPpmk+YXFdsJkABYCZPqiAOYAgpX+JSGEaBAqRQNgg40urs3WEVUFRWidPX3zI8FMUzNoc0NeS77bXb39Q8MAdBAPZHQAqgjU+gDCtTBuF3WACU3gA5ZgmACyAFE0NCgUCMEC0IBeDcApTtoAAeTxE9QArmA8DAZKJUmgrm0tjtuqC8VAAEYGUZccaeY4qMB0xn6JYAq7rSkdG60hlMw6MdmnO4LC4aVhXaki7mPZ6vGjCTGpPmZDRVCBkZDdNDOGCuXK8Sl0HAwB44OiDE2udIgLQgDKgSCwRDGNCgujYAx2iAEYiZSjUCZMNiVUPWQQiMSSaRyRQqZhqZ06MB6Qw+8oWWO8caBdyR7yZsPZc2tTbFUpofOVaq1epTJrhSIGfR0fQAQn5kQAtOwAH5jgfyKjoADk4Zgskj09xNjoxRbwkGkHp1vIFjnaGn+enDwyeVr1120pGY3sZclZwWvJWE8FF9u+2G4vvV9l8pab72e4nhed5PgMX44H+FYOzQEFwShWF4URZF0SxHFhHxQliVJAgKXPRUuSZG8HDZaYOUInk5W1NZIlfAjRX0T87zIqV9l-BVhQolUQPVTU8Goxo9QNI1HWrDZCitG07QdU1nVdDJPXgJBUDQYY4BMORC33Txoy4QtSHjUQJCkBdU1UAR1B1U8ExzIwVMbfSbHsIIywgMAKFYVYi2c0tPDcjyvPkzJX3rRsuGbOoGiaUAgo9aAlJ9NSNIAJksLNbLzcwxP8Fz-K8vwfJUPLnRrdpIzKLLwpqSK22aIKgA