TypeScript事件订阅器类型推断问题:函数参数错误为事件联合类型
问题描述
我正在实现eventSubscriber工具,功能如下:
- 第一个参数是接收函数对象的回调,返回事件与处理函数的关联列表(支持Ctrl+点击跳转至函数实现);
- 第二个参数是事件处理函数对象,期望处理函数的参数类型能自动从其关联的事件类(原型包含
payload属性)推断而来。
但当前实现存在bug:当关联不同事件到不同处理函数时,所有处理函数的参数类型都会被推断为所有关联事件的联合类型,而非对应的单个事件。例如sendEmail关联UserRegisteredEvent,但其参数类型却被推断为UserRegisteredEvent | UserUnregisteredEvent。
当前的类型定义:
const eventSubscriber = < TFunctions extends { [key in string]: (event: any) => Promise<void> }, TSubscriptions extends { event: { name: string; prototype: { payload: any } }; function: TFunctions[keyof TFunctions]; } >( subscriptions: (functions: TFunctions) => TSubscriptions[], functions: { [key in keyof TFunctions]: ( event: TSubscriptions["function"] extends TFunctions[key] ? TSubscriptions["event"] extends infer UEvent ? UEvent extends any ? UEvent : never : never : never, ) => Promise<void>; }, ) => { //... };
我认为问题出在event: TSubscriptions["function"] extends TFunctions[key]这部分——由于所有Function类型都能互相extends,导致该条件恒为true,从而返回所有事件的联合类型。需要更严格的函数类型比较,或者其他可行解决方法。
解决方案
核心问题是TSubscriptions被推断为所有订阅项的联合类型,导致通过TSubscriptions["function"]和TSubscriptions["event"]取到的是所有函数和事件的联合。需要让TypeScript能为每个处理函数单独匹配对应的事件,而非统一处理联合类型。
修正后的类型定义如下:
const eventSubscriber = < TEvents extends { name: string; prototype: { payload: any } }, TFuncMap extends Record<string, (event: any) => Promise<void>>, TSubs extends { [K in keyof TFuncMap]: { event: TEvents; function: TFuncMap[K] } }[keyof TFuncMap][] >( subscriptions: (functions: TFuncMap) => TSubs, functions: { [K in keyof TFuncMap]: ( event: Extract<TSubs[number], { function: TFuncMap[K] }>["event"] ) => Promise<void>; }, ) => { //... };
关键改动说明
- 拆分泛型参数:将事件类型
TEvents、函数映射TFuncMap和订阅列表TSubs分开定义,避免联合类型的混淆。 - 精准匹配事件:使用
Extract<TSubs[number], { function: TFuncMap[K] }>["event"],为每个函数键K从订阅列表中提取仅匹配当前函数的事件类型,而非取所有事件的联合。 - 订阅列表类型约束:通过
{ [K in keyof TFuncMap]: { event: TEvents; function: TFuncMap[K] } }[keyof TFuncMap][]确保每个订阅项的函数都属于TFuncMap,同时保留事件与函数的一对一关联。
效果验证
修正后,每个处理函数的参数类型会被精准推断为其关联的单个事件类型:
sendEmail的参数类型仅为UserRegisteredEventlogUnregister的参数类型仅为UserUnregisteredEvent
内容的提问来源于stack exchange,提问作者czlowiek488
相关产品推荐
相关产品推荐

