You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 19:15:03