TypeScript映射可变泛型时如何保留事件名与对应数据的类型关联
解决方案
你当前的写法中,Listener返回的监听函数没有将入参的eventName和回调参数data做类型绑定,当传入联合类型的事件时,TS只会将data推导为所有事件data字段的联合类型,无法和实际传入的事件名做关联。
实现方案
通过工具类型将事件联合类型转换为「事件名-对应data类型」的映射表,再给监听函数添加泛型做类型绑定即可,修改后的完整代码如下:
interface DefaultEventBody { eventName: string data: unknown } interface ChristmasEvent extends DefaultEventBody { eventName: 'christmas', data: { numberOfPresentsGiven: number } } interface EasterEvent extends DefaultEventBody { eventName: 'easter', data: { numberOfEggsGiven: number } } // 工具类型:将事件联合类型转换为事件名到data类型的映射 type EventMap<T extends DefaultEventBody> = { [K in T['eventName']]: Extract<T, { eventName: K }>['data'] } export function Listener <EventBody extends DefaultEventBody> () { type Events = EventMap<EventBody> // 给返回的监听函数添加泛型,绑定事件名和对应data类型 return <K extends keyof Events>(eventName: K, fn: (data: Events[K]) => void) => { // 内部绑定 emitter 时可以加 as any 规避类型校验,不影响外部调用类型提示 // someEmitter.on(eventName, fn as any) } } const spoiltBrat = { on: Listener<EasterEvent|ChristmasEvent>() } // 此时可以正确推断data类型为 ChristmasEvent['data'] spoiltBrat.on('christmas', (data) => { console.log(data.numberOfPresentsGiven) // 无类型报错 }) // 监听easter事件时也会自动推断对应data类型 spoiltBrat.on('easter', (data) => { console.log(data.numberOfEggsGiven) // 无类型报错 })
实现原理
EventMap工具类型会遍历所有传入事件的eventName作为键,通过Extract匹配到对应事件的data类型作为值,把分散的事件联合类型整合成明确的类型映射表- 返回的监听函数增加了泛型参数
K,TS会根据你实际传入的eventName自动推导K的具体值,回调的data类型会自动绑定为映射表中K对应的类型,实现了事件名和回调参数的类型关联
内容的提问来源于stack exchange,提问作者shennan
相关产品推荐
相关产品推荐

