侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1824 篇文章
  • 累计收到 0 条评论

实现一个类型安全的EventEmitter类

2025-12-12 / 0 评论 / 4 阅读

题目

实现一个类型安全的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替代数组存储监听器(需注意执行顺序变化)