为何该TypeScript泛型函数无法识别两个参数类型兼容?
核心问题:泛型的「全场景有效性」检查 + 参数逆变
你遇到的错误本质是TypeScript泛型函数的类型检查逻辑和函数参数的逆变特性共同导致的:
泛型参数的范围假设
你定义的泛型参数EventName extends WrappedEventName,TypeScript会默认它可以是WrappedEventName的任意子类型——包括整个联合类型"mousemove" | "wheel"。它不会假设EventName是某个单一的具体值,而是要求函数体代码对所有可能的EventName都能合法运行。函数参数的逆变特性
在TypeScript中,函数的参数类型是逆变的。当EventName是联合类型时,callback的类型(event: WrappedEvent<EventName>) => void会被逆变处理:原本WrappedEvent<EventName>是WrappedMouseEvent | WrappedWheelEvent,但逆变后,函数参数类型会变成WrappedMouseEvent & WrappedWheelEvent(交叉类型)。这是因为只有能同时接受两种WrappedEvent的函数,才能适配所有可能的EventName场景。类型不匹配的根源
你创建的{ originalEvent },TypeScript只能推断它的类型是{ originalEvent: MouseEvent | WheelEvent }。这个类型既无法满足WrappedMouseEvent(当originalEvent是WheelEvent时),也无法满足WrappedWheelEvent(当originalEvent是MouseEvent时),更不可能满足两者的交叉类型——所以TypeScript抛出了类型不兼容的错误。
简单来说,你原本期望EventName把originalEvent和callback绑定到同一具体类型,但TypeScript在泛型函数内部无法建立这种一对一的关联,它必须保证代码对所有可能的EventName都有效,而你的写法无法满足这个严格要求。
修复方案
方案1:函数重载(最安全)
通过明确重载签名,为每种事件类型指定对应的参数和回调类型,让TypeScript能精准匹配:
type WrappedMouseEvent = { originalEvent: MouseEvent; }; type WrappedWheelEvent = { originalEvent: WheelEvent; }; type EventNameToWrappedEventMap = { mousemove: WrappedMouseEvent; wheel: WrappedWheelEvent; }; // 定义重载签名 function wrappedCallback( originalEvent: MouseEvent, callback: (event: WrappedMouseEvent) => void ): void; function wrappedCallback( originalEvent: WheelEvent, callback: (event: WrappedWheelEvent) => void ): void; // 通用实现 function wrappedCallback( originalEvent: MouseEvent | WheelEvent, callback: (event: WrappedMouseEvent | WrappedWheelEvent) => void ) { callback({ originalEvent }); }
方案2:类型断言(简洁但需自行保证安全)
如果你能确保代码逻辑的正确性,可以用类型断言告诉TypeScript当前的对象就是对应的WrappedEvent<EventName>:
type WrappedMouseEvent = { originalEvent: MouseEvent; }; type WrappedWheelEvent = { originalEvent: WheelEvent; }; type EventNameToWrappedEventMap = { mousemove: WrappedMouseEvent; wheel: WrappedWheelEvent; }; type WrappedEventName = keyof EventNameToWrappedEventMap; type WrappedEvent<EventName extends WrappedEventName> = EventNameToWrappedEventMap[EventName]; function wrappedCallback<EventName extends WrappedEventName>( originalEvent: WrappedEvent<EventName>["originalEvent"], callback: (event: WrappedEvent<EventName>) => void ) { callback({ originalEvent } as WrappedEvent<EventName>); }
内容的提问来源于stack exchange,提问作者dipea

