Typescript 中与通用容器混淆的错误

Posted

技术标签:

【中文标题】Typescript 中与通用容器混淆的错误【英文标题】:Confusing error with generic container in Typescript 【发布时间】:2020-02-07 01:07:26 【问题描述】:

考虑这段代码:

class Base 

class Foo<T extends Base> 
  constructor(public callback: (data: T) => void) 
  


let map: Map<number, Foo<Base>> = new Map();

function rpcCall<T extends Base>(
  callback: (data: T) => void,
): void 
  map.set(0, new Foo<T>(callback));

它给了我这个错误:

Foo&lt;T&gt; 类型的参数不能分配给Foo&lt;Base&gt; 类型的参数。

类型 Base 不可分配给类型 T

Base 可分配给T 类型的约束,但T 可以使用不同的约束Base 子类型实例化。

我不明白为什么这不起作用。错误消息似乎是正确的,但我不明白为什么这是一个错误。我想要 T 被允许成为约束Base 的不同子类型。

另外,这确实工作:

class Base  

class Foo<T extends Base> 
  constructor(public callback: (data: T) => void) 
  


class Foo2<T extends Base> 


let map: Map<number, Foo<Base> | Foo2<Base>> = new Map();

function rpcCall<T extends Base>(
  callback: (data: T) => void,

): void 
  map.set(0, new Foo<T>(callback));

【问题讨论】:

你有什么计划来处理这个问题:class BaseWithRaisins extends Base raisins = 100; rpcCall((x: BaseWithRaisins) =&gt; console.log(x.raisins.toFixed())); map.get(0)!.callback(new Base());?这不会引发编译时错误,但你会在运行时爆炸,因为 Foo&lt;Base&gt; 的回调必须接受 any Base,但你给它的东西只能处理 @987654336 @. 可能想让map成为Map&lt;number, Foo&lt;any&gt;&gt;,但我不知道你实际上能做什么 与它。大概您的用例在某处涉及从map 中提取内容并调用回调,此时您将不知道它接受什么类型的参数。您能否提供一个更完整的用例以及您希望如何使用地图的示例代码? 【参考方案1】:

我将在您的代码中添加一些属性来说明这一点。

interface Base 
    bar: string;


interface Child extends Base 
    magic: number;


class Foo<T extends Base> 
    constructor(public callback: (data: T) => void) 
        // ' bar: string; ' is assignable to the constraint of type 'T',
        // but 'T' could be instantiated with a different subtype of constraint 'Base'.
        callback( bar: "sweet" );
    


let map: Map<number, Foo<Base>> = new Map();

rpcCall((d: Child) => d.magic);

function rpcCall<T extends Base>(callback: (data: T) => void) 
    // 'Base' is assignable to the constraint of type 'T',
    // but 'T' could be instantiated with a different subtype of constraint 'Base'
    map.set(0, new Foo(callback));

哦,亲爱的。更多错误!

如您所见,在 Foo 内部,我尝试调用您定义的回调,并向其传递了一个扩展 Base 的对象,但它向我抛出了一个错误。

如果回调期待Child 怎么办?当然,只要Foo 关心,任何扩展Base 的东西都可以,但是你怎么知道回调本身期待什么?

如果您查看我对rpcCall 的使用情况,我合法地给了它一个回调,期待Child,我正在尝试使用magic 属性(在我的Child extends Base 界面中标记为必需) )。

基本上在某些时候可能会尝试使用Base 上不存在的东西。

如果你用简单的Base 替换泛型,一些错误就会消失,但是像rpcCall((d: Child) =&gt; d.magic) 这样的操作将被禁止。如果您不需要这些区域的非基础属性,这对您来说可能没问题。


您提供的第二个版本有效,因为 Foo2 是一个空类(泛型被完全忽略,事实上,因为您不使用它)。

一个空类相当于,它基本上接受除nullundefined 之外的所有内容(据我所知)。对于联合类型,任何“宽松”的参数都将优先于更严格的参数。

以下都是等价的(在这种情况下):

Map<number, Foo<Base> | Foo2<Base>>
Map<number, Foo<Base> | >
Map<number, Foo<Foo2<Base>>
Map<number, Foo<>

事实上,如果您将| any 传送到任何联合的末尾,则该联合实际上会变为any

【讨论】:

【参考方案2】:

这是一个有效的错误。在--strictFunctionTypes 函数类型参数的位置被逆变检查。

如果 TS 允许您传递需要 Base 的任意子类型的函数,Foo 构造函数仍然可以仅使用普通的 Base 调用它,并且期望特定子类型的函数将失败.

您可以禁用--strictFunctionTypes 以使您的代码编译,但您会失去一些类型安全性,如上所述。详情请见https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-6.html

【讨论】:

以上是关于Typescript 中与通用容器混淆的错误的主要内容,如果未能解决你的问题,请参考以下文章

Array.find() 方法在 TypeScript 中与 req.param 一起使用

泛型函数子类型约束错误和混淆

Typescript中的函数定义语法混淆

使用 get set "exited with code 1" 编译 TypeScript 错误代码

TypeScript: type alias 与 interface

TypeScript - 通用约束可以提供“允许的”类型吗?