如何实现TypeScript类型EventMapOf<T>提取EventTarget的事件映射?
提取EventTarget子类的自定义事件映射类型:EventMapOf实现方案
实现代码
// 辅助类型:根据事件名提取对应的事件类型 type EventType<T extends EventTarget, K extends string> = Parameters<Extract<T['addEventListener'], (type: K, ...args: any[]) => void>>[1] extends (evt: infer E) => void ? E : never; // 核心类型:从EventTarget子类中提取事件映射 type EventMapOf<T extends EventTarget> = { [K in Exclude<Parameters<T['addEventListener']>[0], string>]: EventType<T, K>; };
原理说明
- 提取事件名联合类型:
Parameters<T['addEventListener']>[0]获取addEventListener方法第一个参数的所有可能类型,对于自定义子类来说,这个类型是「自定义事件名字面量 + string」的联合。 - 过滤基础重载干扰:用
Exclude<..., string>剔除基础EventTarget重载中的string类型,只保留子类自定义的事件名字面量。 - 匹配对应事件类型:
EventType<T, K>通过Extract筛选出匹配当前事件名K的重载,再从监听函数的参数中推断出对应的事件类型E。 - 构建事件映射对象:通过索引类型遍历过滤后的事件名,将每个事件名映射到对应的事件类型,最终得到完整的自定义事件映射。
示例验证
// 定义自定义事件映射 interface FooEventMap { 'custom-click': CustomEvent<{ id: number }>; 'data-update': Event; } // 实现EventTarget子类 interface Foo extends EventTarget { addEventListener<K extends keyof FooEventMap>( type: K, listener: (evt: FooEventMap[K]) => void, options?: boolean | AddEventListenerOptions ): void; // 保留基础EventTarget的重载 addEventListener( type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions ): void; } // 提取事件映射 type FooExtractedEventMap = EventMapOf<Foo>; // 结果:{ 'custom-click': CustomEvent<{ id: number }>; 'data-update': Event; }
解决之前的问题
- 基础EventTarget的重载会将事件名合并为
string类型,通过Exclude可以精准过滤掉这个通用类型,分离出自定义字面量事件名。 - 泛型约束推断失败是因为重载函数的类型无法直接被自动解构,通过
Extract筛选特定重载、结合infer推断事件类型,可以绕过这个限制。
内容的提问来源于stack exchange,提问作者KRyan
相关产品推荐
相关产品推荐

