如何在辅助函数中映射泛型Record键值时保留类型推断?
类型化事件发射器的辅助订阅函数类型推断问题
我有一个类型化事件发射器:
type Listener<T> = (arg: T) => void; class TypedEventEmitter<Events extends {}> { public on<E extends keyof Events>(event: E, listener: Listener<Events[E]>) { // ... } }
它的使用方式如下:
type MyEvents = { click: { x: number, y: number }; keydown: { code: string }; } class MyClass extends TypedEventEmitter<MyEvents> {} const myClass = new MyClass(); myClass.on('click', data => console.log(data.x)); // 正常工作 myClass.on('keydown', data => console.log(data.x)); // 错误:类型“{ code: string; }”上不存在属性“x”
目前一切正常,但我想创建一个辅助函数来订阅该事件发射器,使用方式如下:
subscribe(myClass, 'click', (data) => { /* ... */ });
我的尝试如下:
function subscribe< T extends TypedEventEmitter<Events>, Events extends {}, E extends keyof Events >(eventEmitter: T, eventName: E, listener: Listener<Events[E]>) { eventEmitter.on(eventName, listener); }
如果显式指定类型参数,它可以正常工作:
subscribe<MyClass, MyEvents, 'click'>(myClass, 'click', (data) => { console.log(data.x); });
但直接使用会报错,无法自动推断类型参数:
subscribe(myClass, 'click', (data) => { // ^ 类型“"click"”的参数不能赋给类型“never”的参数 });
我还尝试了另一种写法,但也存在问题:
function subscribe2< T extends TypedEventEmitter<{}> >(eventEmitter: T, ...[eventName, listener]: Parameters<T['on']>) { eventEmitter.on(eventName, listener); } subscribe2(myClass, 'click', (data) => { console.log(data.x); // ^ 类型“{ x: number; y: number; } | { code: string; }”上不存在属性“x” });
有没有办法无需显式指定类型参数就能实现该功能?
解决方案
可以通过调整泛型参数的约束和顺序,让TypeScript正确自动推断类型:
方法一:调整泛型参数顺序
将Events作为第一个泛型参数,让TypeScript先从事件发射器实例推断出事件类型,再推导具体事件名和监听器类型:
function subscribe< Events extends {}, E extends keyof Events, T extends TypedEventEmitter<Events> >(eventEmitter: T, eventName: E, listener: Listener<Events[E]>) { eventEmitter.on(eventName, listener); }
调用时即可自动推断类型:
subscribe(myClass, 'click', (data) => { console.log(data.x); // 正确识别data为{ x: number, y: number } }); subscribe(myClass, 'keydown', (data) => { console.log(data.code); // 正确识别data为{ code: string } });
方法二:提取事件发射器的内置事件类型
使用条件类型提取TypedEventEmitter实例的事件类型,简化泛型定义:
type ExtractEvents<T> = T extends TypedEventEmitter<infer E> ? E : never; function subscribe< T extends TypedEventEmitter<{}>, E extends keyof ExtractEvents<T> >(eventEmitter: T, eventName: E, listener: Listener<ExtractEvents<T>[E]>) { eventEmitter.on(eventName, listener); }
这种写法同样能实现自动推断,且无需显式声明Events参数。
原写法失败原因
- 第一种尝试中,泛型参数顺序为
T→Events→E,TypeScript无法从T反向推断出Events的具体类型,导致E被推断为never。 - 第二种尝试中,
Parameters<T['on']>会将所有事件的类型合并为联合类型,无法区分具体事件对应的参数类型。
内容的提问来源于stack exchange,提问作者mdatsev
相关产品推荐
相关产品推荐

