TypeScript泛型类中如何正确实现emit方法重载?
TypeScript泛型类中EventEmitter的emit方法重载正确写法
需求很明确:泛型EventEmitter类的emit方法要根据事件类型自动校验参数数量——如果对应事件是无参函数,emit只需要传事件名;如果是带参函数,必须传入匹配Parameters<T[evt]>类型的参数数组。之前尝试的重载写法没达到预期,以下是正确实现方式。
现有代码
interface IEventListeners { [key: string]: (...args: any[]) => void | false } export class EventEmiiter< T extends IEventListeners > { private _listeners: { [K in keyof T]?: T[K][] } = {}; public on<E extends keyof T>(evt: E, executor: T[E]) { const listener = this.getListener(evt); listener.push(executor); return () => { this.off(evt, executor); } } public off<E extends keyof T>(evt: E, executor: T[E]) { const listener = this.getListener(evt); const index = listener.findIndex(exe => exe === executor); listener.splice(index, 1); } public emit<E extends keyof T>(evt: E, params?: Parameters<T[E]>) { const listener = this.getListener(evt); const iter = listener[Symbol.iterator](); let iterObj: ReturnType<typeof iter['next']>; let falseValue: undefined | false; while (iterObj = iter.next()) { const result = iterObj.value(...(params || [])) if (result === false) { falseValue = false; } if (iterObj.done) { break; } } return falseValue; } private getListener<E extends keyof T>(evt: E) { return this._listeners[evt] = this._listeners[evt] || []; } /** * React Component Hook * @describe 用于订阅的hooks, 不需考虑事件的卸载, 组件卸载时自动卸载事件 * */ public useSubscribe<E extends keyof T>(evt: E, executor: T[E]) { const off = this.on(evt, executor); useUnmount(() => { off(); }) return off; } } type Subscribers = { onOk: () => void; onClick: (val: number) => void; } const emitter = new EventEmiiter<Subscribers>; emitter.emit('onOk') // 这里期望不报错,onOk是无参函数 emitter.emit('onClick'); // 这里期望报错,onClick需要参数
尝试的重载声明(未生效)
class EventEmitter { public emit<E extends keyof T>(evt: E) public emit<E extends keyof T>(evt: E, params: Parameters<T[E]>) public emit<E extends keyof T>(evt: E, params?: Parameters<T[E]>) }
正确实现方案
核心是通过条件类型区分无参和带参事件,让TypeScript能根据事件名自动推断所需参数:
- 先修正类名拼写错误(
EventEmiiter→EventEmitter) - 为emit方法定义两个重载签名,分别对应无参和带参场景
- 实现签名保持兼容,但通过条件类型约束参数的可选性
完整修正代码
interface IEventListeners { [key: string]: (...args: any[]) => void | false } export class EventEmitter<T extends IEventListeners> { private _listeners: { [K in keyof T]?: T[K][] } = {}; public on<E extends keyof T>(evt: E, executor: T[E]) { const listener = this.getListener(evt); listener.push(executor); return () => { this.off(evt, executor); }; } public off<E extends keyof T>(evt: E, executor: T[E]) { const listener = this.getListener(evt); const index = listener.findIndex(exe => exe === executor); if (index !== -1) { listener.splice(index, 1); } } // 重载1:无参事件,只传事件名 public emit<E extends keyof T>( evt: E ): Parameters<T[E]> extends [] ? void : never; // 重载2:带参事件,必须传事件名+参数数组 public emit<E extends keyof T>( evt: E, params: Parameters<T[E]> ): Parameters<T[E]> extends [] ? never : void; // 实现签名,兼容所有重载 public emit<E extends keyof T>(evt: E, params?: Parameters<T[E]>) { const listener = this.getListener(evt); let falseValue: undefined | false; for (const handler of listener) { const result = handler(...(params || [])); if (result === false) { falseValue = false; } } return falseValue; } private getListener<E extends keyof T>(evt: E): T[E][] { return (this._listeners[evt] = this._listeners[evt] || []); } public useSubscribe<E extends keyof T>(evt: E, executor: T[E]) { const off = this.on(evt, executor); // 假设useUnmount是已定义的React钩子 useUnmount(() => { off(); }); return off; } } type Subscribers = { onOk: () => void; onClick: (val: number) => void; }; const emitter = new EventEmitter<Subscribers>(); emitter.emit('onOk'); // ✅ 正确,无参事件不需要传参数 emitter.emit('onClick'); // ❌ 报错:缺少必需的参数params emitter.emit('onClick', [123]); // ✅ 正确,参数类型匹配 emitter.emit('onOk', []); // ❌ 报错:无参事件不能传参数
关键说明
- 重载签名通过
Parameters<T[E]> extends []判断事件是否无参,TypeScript会根据传入的事件名自动匹配对应的重载 - 实现签名中的
params?只是为了兼容所有重载,实际调用时TS会根据事件类型强制要求是否传参数 - 修正了原代码中
while循环的逻辑,改用for...of更简洁可靠
内容的提问来源于stack exchange,提问作者tttinkl
相关产品推荐
相关产品推荐

