为啥在泛型上使用 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&lt;T&gt; 而不是构造函数签名。 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 的类 - 它需要printprint2 方法才能将类本身分配给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&lt;T&gt; 以进行类型检查所需的类型更弱,因为InstanceType&lt;T&gt; 确实 显式地创建了一个类型变量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 是错误的的主要内容,如果未能解决你的问题,请参考以下文章

17. Scala泛型上下界视图界定上下文界定

在泛型方法中处理类型创建

为啥我不能在 C# 应用程序中使用泛型类型作为入口点?

在泛型类中传递泛型参数

为啥 TS 中的泛型接口不能正确推断类型?

C# 在泛型中重复使用类型时缩短语法?