← 返回导航

事件总线类型

业务类型 · 实战

场景说明

事件总线(EventBus)是前端解耦组件通信的常见方案:一处 emit,多处 on 监听。难点在于"事件名与回调参数一一对应"——纯 JS 实现里 emit 时传错参数、on 时写错事件名都不会报错,运行时才暴露问题。本 demo 用映射类型把事件名映射到参数类型,让 emit、on、off 都被类型系统强约束,事件名拼错或参数类型不符都会编译期报错。

完整代码


      
// 事件总线类型:事件名与参数类型一一映射
// emit / on / off 全部由类型系统强约束

// 1. 事件映射表:键为事件名,值为回调参数类型
interface EventMap {
  login: { userId: string; token: string };
  logout: undefined;
  message: { from: string; content: string };
  notify: string;
}

// 2. 回调类型:无参事件用 () => void,有参事件用 (payload) => void
type Handler<P> = [P] extends [undefined] ? () => void : (payload: P) => void;

// 3. 事件总线类:泛型约束事件映射表
class EventBus<M extends Record<string, unknown>> {
  private handlers: { [K in keyof M]?: Set<Handler<M[K]>> } = {};

  // on:事件名必须是 M 的键,回调参数类型自动推导
  on<K extends keyof M>(event: K, handler: Handler<M[K]>): this {
    (this.handlers[event] ??= new Set()).add(handler);
    return this;
  }

  // off:取消订阅,签名与 on 一致
  off<K extends keyof M>(event: K, handler: Handler<M[K]>): this {
    this.handlers[event]?.delete(handler);
    return this;
  }

  // emit:无参事件可不传参,有参事件参数类型必须匹配
  emit<K extends keyof M>(...args: [undefined] extends [M[K]] ? [event: K] : [event: K, payload: M[K]]): void {
    const set = this.handlers[args[0]];
    if (!set) return;
    const payload = args.length > 1 ? args[1] : undefined;
    set.forEach(function (h) { (h as Function)(payload); });
  }
}

// 应用:创建强类型事件总线实例
const bus = new EventBus<EventMap>();

bus.on('login', function (p) { // p 自动推导为 { userId: string; token: string }
  console.log(p.userId, p.token);
});

bus.on('logout', function () { // 无参事件回调不带参数
  console.log('已退出登录');
});

bus.emit('login', { userId: 'u_1', token: 'tk' }); // 参数类型匹配
bus.emit('logout'); // 无参事件无需传 payload

关键点解析

使用示例与类型推导


      
// 事件名拼错会被编译期拦截
// bus.emit('logn', { userId: 'u_1', token: 'tk' });
// 报错:logn 不在 EventMap 的键中

// payload 类型不符会被拦截
// bus.emit('login', { userId: 'u_1' });
// 报错:缺少 token 字段

// 无参事件多传参数会被拦截
// bus.emit('logout', 'extra');
// 报错:logout 期望 0 个参数

// notify 事件参数为 string
bus.on('notify', function (msg) { // msg 自动推导为 string
  console.log('通知:' + msg);
});
bus.emit('notify', '系统维护中');

// 链式调用:on 返回 this
bus.on('message', function (m) { console.log(m.from, m.content); })
   .on('message', function (m) { console.log('第二条订阅'); });

// off 取消单个订阅
const cb = function (m: { from: string; content: string }) { console.log(m); };
bus.on('message', cb);
bus.off('message', cb);
在 Playground 中尝试