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<T>
类型的参数不能分配给Foo<Base>
类型的参数。类型
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) => console.log(x.raisins.toFixed())); map.get(0)!.callback(new Base());
?这不会引发编译时错误,但你会在运行时爆炸,因为 Foo<Base>
的回调必须接受 any Base
,但你给它的东西只能处理 @987654336 @.
你可能想让map
成为Map<number, Foo<any>>
,但我不知道你实际上能做什么 与它。大概您的用例在某处涉及从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) => d.magic)
这样的操作将被禁止。如果您不需要这些区域的非基础属性,这对您来说可能没问题。
您提供的第二个版本有效,因为 Foo2
是一个空类(泛型被完全忽略,事实上,因为您不使用它)。
一个空类相当于,它基本上接受除
null
和undefined
之外的所有内容(据我所知)。对于联合类型,任何“宽松”的参数都将优先于更严格的参数。
以下都是等价的(在这种情况下):
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 一起使用
使用 get set "exited with code 1" 编译 TypeScript 错误代码