TypeScript约束匹配问题:MessagePort无法适配自定义MsgPort接口
问题:TypeScript泛型约束中MessagePort无法匹配自定义MsgPort接口?
场景复现
尝试让Child类继承泛型类Sup<MessagePort>,其中Sup的泛型参数X被约束为自定义的MsgPort接口,代码如下:
interface MsgEvent { readonly data: any; // 1 to 1 copy from MessageEvent<any> } interface MsgPort { postMessage: (message: any) => void; // 1 to 1 copy from MessagePort onmessage: ((ev: MsgEvent) => any) | null; // 1 to 1 copy from MessagePort } class Sup<X extends MsgPort> { } class Child extends Sup<MessagePort> { }
虽然MessagePort包含MsgPort的所有属性,理论上应满足约束,但TypeScript抛出如下错误:
Type 'MessagePort' does not satisfy the constraint 'MsgPort'. Types of property 'onmessage' are incompatible. Type '((this: MessagePort, ev: MessageEvent<any>) => any) | null' is not assignable to type '((ev: MsgEvent) => any) | null'. Type '(this: MessagePort, ev: MessageEvent<any>) => any' is not assignable to type '(ev: MsgEvent) => any'. Types of parameters 'ev' and 'ev' are incompatible. Type 'MsgEvent' is missing the following properties from type 'MessageEvent<any>': lastEventId, origin, ports, source, and 23 more.ts(2344)
已确认this: MessagePort部分不影响结果。而当直接使用MsgEvent与MessageEvent<any>进行泛型约束时,代码可正常运行:
class Sup<X extends MsgEvent> { } class Child extends Sup<MessageEvent<any>> { }
按照TypeScript规则,更宽泛的类型应可赋值给更窄的约束类型,因此疑惑这是TypeScript的Bug还是自身代码存在问题?
问题原因
这不是TypeScript的Bug,而是函数参数的逆变特性导致的:
MsgPort的onmessage类型是((ev: MsgEvent) => any) | null,而浏览器原生MessagePort的onmessage是((this: MessagePort, ev: MessageEvent<any>) => any) | null。- 函数类型的兼容性遵循逆变原则:若要让函数A赋值给函数B,A的参数类型必须是B参数类型的父类型(更宽泛)。这样才能保证传入B的参数时,A能正确接收处理。
- 这里
MsgEvent是MessageEvent<any>的子集(更窄),所以(ev: MessageEvent<any>) => any不能赋值给(ev: MsgEvent) => any——当给后者传入仅符合MsgEvent的对象时,前者的函数期望的是拥有更多属性的MessageEvent<any>,会出现属性缺失的问题。 - 而直接用
MsgEvent作为泛型约束时,MessageEvent<any>是MsgEvent的超集,符合泛型约束的协变检查要求(检查类型是否是约束类型的子类型),因此不会报错。
解决方案
有两种常见的修复方式:
- 对齐原生类型定义
让自定义MsgPort接口的onmessage参数类型和原生MessagePort保持一致:interface MsgPort { postMessage: (message: any) => void; onmessage: ((ev: MessageEvent<any>) => any) | null; } - 兼容参数类型范围
如果需要保留MsgEvent定义,可以扩大onmessage的参数类型范围,使其能接收MsgEvent或更宽泛的MessageEvent<any>:interface MsgPort { postMessage: (message: any) => void; onmessage: ((ev: MsgEvent | MessageEvent<any>) => any) | null; }
内容的提问来源于stack exchange,提问作者Sonaht
相关产品推荐
相关产品推荐

