You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中实现EventBus的emit方法动态参数类型校验与自动补全

解决TypeScript EventBus的事件自动补全与类型校验问题

你的问题核心在于没有建立事件名与对应回调参数的类型关联,原代码用any和宽泛的string | symbol类型导致TypeScript无法追踪事件的具体参数结构,自然没法提供自动补全和类型校验。

下面是改造后的实现,通过泛型约束事件类型映射,让emit、on、off方法都能基于事件名自动推导参数类型:

// 定义事件映射类型:键是事件名,值是该事件回调的参数元组
type EventMap = Record<string | symbol, any[]>;

class EventBus<T extends EventMap = {}> {
  private listeners: { [K in keyof T]?: ((...args: T[K]) => void)[] } = {};

  on<K extends keyof T>(event: K, callback: (...args: T[K]) => void) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(callback);
  }

  off<K extends keyof T>(event: K, callback: (...args: T[K]) => void) {
    const eventListeners = this.listeners[event];
    if (!eventListeners) {
      throw new Error(`Нет события: ${event.toString()}`);
    }
    this.listeners[event] = eventListeners.filter(listener => listener !== callback);
  }

  emit<K extends keyof T>(event: K, ...args: T[K]) {
    const eventListeners = this.listeners[event];
    if (!eventListeners) {
      throw new Error(`Нет события: ${event.toString()}`);
    }
    eventListeners.forEach(listener => {
      listener(...args);
    });
  }
}

使用示例

当你定义具体的事件类型后,TypeScript会自动提供事件名补全和参数校验:

// 定义你的业务事件映射
type AppEvents = {
  'user-login': [username: string, timestamp: number];
  'message-sent': [content: string, recipientId: string];
};

const bus = new EventBus<AppEvents>();

// 自动补全事件名,参数类型严格校验
bus.on('user-login', (username, timestamp) => {
  console.log(`${username} logged in at ${timestamp}`);
});

// 正确调用:参数类型匹配
bus.emit('user-login', 'Alice', Date.now());

// 错误示例:TypeScript会报错,参数数量/类型不匹配
bus.emit('user-login', 'Bob'); // 缺少timestamp参数
bus.emit('message-sent', 123, 'user123'); // content应该是string

关键改造点说明

  1. 泛型事件映射:通过T extends EventMap让类接收一个事件类型映射,每个事件名对应其参数元组,TypeScript能据此追踪每个事件的参数结构。
  2. 索引类型约束:on/off/emit方法用K extends keyof T约束事件名,确保只能使用预定义的事件名,实现自动补全。
  3. 参数类型绑定:回调函数和emit的参数都使用T[K],确保事件名和参数类型严格对应,避免传参错误。

原代码中typeof this.listeners的问题在于,listeners初始是空对象,TypeScript无法推断出未来会添加的事件名,而通过显式的泛型事件映射,我们提前告诉TypeScript所有可能的事件及其参数类型,从而实现预期的类型功能。

内容的提问来源于stack exchange,提问作者Krezo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 16:55:18