为啥在泛型上使用 InstanceType 是错误的
Posted
技术标签:
【中文标题】为啥在泛型上使用 InstanceType 是错误的【英文标题】:Why is it wrong to use InstanceType on generics为什么在泛型上使用 InstanceType 是错误的 【发布时间】:2021-05-30 00:09:00 【问题描述】:为什么在泛型上使用InstanceType
是错误的?是协变的还是逆变的?
interface Ctor
new(): Instance;
interface Instance
print(): void;
function f1<T extends Ctor>(ctor: T)
// Error: Type 'Instance' is not assignable to Type 'InstanceType<T>'
const ins: InstanceType<T> = new ctor();
ins.print();
function f2(ctor: Ctor)
// No error
const ins: InstanceType<Ctor> = new ctor();
ins.print();
Playground Link
【问题讨论】:
这是一个非常有趣的问题!很少有 TS 问题难倒我,但我难住了。 这并没有说明问题,但同样可以观察到函数类型和ReturnType<T>
而不是构造函数签名。 Playground Link
【参考方案1】:
因为您说 T 扩展了 Ctor,这意味着它不完全是 Ctor。它可能有一个返回 Instance|Instance2 的 new。 Typescript 无从得知。而且由于 Ctor 声明它总是从它的构造函数返回一个 Instance,它不知道正确派生的正确类型。
如果您希望它与泛型和派生类型一起使用,则需要允许它使用 Generic 参数推断派生类型。
interface Ctor<TInstance extends Instance>
new(): TInstance;
interface Instance
print(): void;
function f1<TInstance extends Instance>(ctor: Ctor<TInstance>)
const ins: TInstance = new ctor();
ins.print();
【讨论】:
“它可能有一个新的返回 Instance|Instance2” 这不是真的;像new(): Instance | Instance2
这样的类型不会扩展new(): Instance
。如果是这样,您可以将构造 Instance2
实例的事物分配给声称构造 Instance
实例的事物。
真的@kaya3 吗?那为什么我可以这样做: interface Tor new(): Instance; 接口 Tor2 扩展了 Tor new(): Instance2; 接口实例 print(): 无效; 接口 Instance2 print2(): 无效;
如果你明确声明类型Ctor2 extends Ctor
,那么你当然有一个子类型;这就是extends
在类型声明中的含义。但是你的Ctor2
不返回Instance | Instance2
,它有两个重载签名,但重载签名不是联合类型,并且重载签名在 Typescript 中通常不是类型安全的——编译器不会检查你是否正在使用它们正确,它只进行一些健全性测试。重载签名在这方面更像是类型断言而不是类型。
至于“真的”,是的,真的,很容易确认new(): Instance | Instance2
不能分配给new(): Instance
。 Playground Link
如果您仍然不确定,请尝试编写一个实现interface Ctor2 extends Ctor
的类 - 它需要print
和print2
方法才能将类本身分配给Ctor2
。你的Ctor2
类型更像是一个交集而不是一个联合,因为它必须构造一个既是Instance
又是Instance2
的东西。 Playground Link【参考方案2】:
这就是我认为的问题所在:Typescript 通过查看ctor
(即T
)的类型来确定表达式new ctor()
的编译时类型,检查T
是否必须具有构造函数签名(确实如此),然后找到T
的构造函数签名返回的内容。但是T
可以是Ctor
的任何子类型,因此它的构造函数签名可以返回Instance
的任何子类型。
在这种情况下,编译器可以做两件合理的事情:
要么发明一个新的类型变量(我们称之为R extends Instance
),让表达式new ctor()
的类型为R
,
或者只是让表达式的类型为Instance
。
Typescript 是后者,可能是因为 Typescript 只会在你告诉它的情况下创建类型变量;它从不暗中这样做。这使表达式的类型比分配给InstanceType<T>
以进行类型检查所需的类型更弱,因为InstanceType<T>
确实 显式地创建了一个类型变量R
(在条件类型中使用infer
子句),它可以是Instance
的任意子类型。
所以表达式new ctor()
的类型是Instance
,但赋值目标的类型注释是(不是真的,而是在某种意义上)一个类型变量R
,它可以是任何Instance
的子类型,因此存在类型错误。
一种可能的解决方案是为返回类型显式创建一个类型变量,如下所示:
function f1<R extends Instance, T extends new(): R>(ctor: T)
const ins: R = new ctor();
ins.print();
也就是说,仅当您的函数以其他某种身份处理 R
时才值得这样做,例如具有涉及 R
的返回类型。否则,您使用ins
的代码必须适用于R
的任何实现,包括Instance
本身,因此您不妨将其类型声明为Instance
。 (同样,如果您的函数在其他任何地方都没有使用T
,那么您不妨改为使用Ctor
参数类型。
【讨论】:
以上是关于为啥在泛型上使用 InstanceType 是错误的的主要内容,如果未能解决你的问题,请参考以下文章