题目
实现一个类型安全的EventEmitter类
信息
- 类型:问答
- 难度:⭐⭐
考点
TypeScript泛型, 类型推断, 事件处理, 类与接口
快速回答
实现一个类型安全的EventEmitter需要:
- 使用泛型定义事件名与回调函数的映射关系
- 通过接口约束事件类型
- 实现on/off/emit核心方法
- 处理函数数组的存储与维护
- 确保类型安全的事件触发
问题背景
在事件驱动编程中,EventEmitter是核心模式。TypeScript的实现需要保证:
- 事件名称与对应回调参数的类型安全
- 避免运行时类型错误
- 支持多个监听器
核心实现
// 1. 定义事件映射接口
type EventMap = Record<string, any>;
// 2. 泛型类定义
class EventEmitter<T extends EventMap> {
private events: {
[K in keyof T]?: ((...args: T[K]) => void)[]
} = {};
// 3. 注册事件
on<K extends keyof T>(event: K, listener: (...args: T[K]) => void) {
(this.events[event] ||= []).push(listener);
return this;
}
// 4. 触发事件
emit<K extends keyof T>(event: K, ...args: T[K]) {
this.events[event]?.forEach(fn => fn(...args));
}
// 5. 移除事件
off<K extends keyof T>(event: K, listener: (...args: T[K]) => void) {
const listeners = this.events[event];
if (listeners) {
this.events[event] = listeners.filter(fn => fn !== listener);
}
return this;
}
}
// 使用示例
interface MyEvents {
'message': [string, number]; // 事件名: 参数元组
'error': [Error];
}
const emitter = new EventEmitter<MyEvents>();
emitter.on('message', (msg, code) => console.log(`${code}: ${msg}`));
emitter.emit('message', 'Hello', 200); // 正确
emitter.emit('error', new Error('Fail')); // 正确
// emitter.emit('message', 100); // 类型错误:缺少string参数关键原理
- 泛型约束:
T extends EventMap确保传入类型符合事件映射结构 - 索引签名:
[K in keyof T]?动态生成事件属性,值类型为函数数组 - 类型推断:
on/emit方法通过事件名自动推断参数类型
最佳实践
- 使用接口明确定义所有事件类型(如
MyEvents) - 通过
||=简化空数组初始化(需要ES2021环境) - 返回
this支持链式调用 - 添加
once()方法实现单次监听(扩展建议)
常见错误
- 类型映射错误:未使用
keyof T约束事件名导致类型不安全 - 参数传递错误:
emit时未展开...args导致参数数组错误传递 - 内存泄漏:未实现
off方法造成监听器堆积
扩展知识
- 重载方法:为
on添加(event: '*', listener)支持通配符事件 - 异步处理:使用
Promise.all实现异步事件触发 - 错误处理:添加
try/catch包裹emit执行过程 - 性能优化:使用
Set替代数组存储监听器(需注意执行顺序变化)