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

TypeScript扩展含Listener的EventEmitter类的类型错误问题

解决方案:用泛型实现可扩展的类型安全EventEmitter

核心问题在于原EventEmitter的事件类型未做泛型约束,导致子类扩展事件时出现类型不兼容。通过将EventEmitter改造为泛型类,让父类和子类各自指定自己的事件类型集合,就能解决这个问题。

修正后的代码示例

方式1:直接定义子类的完整事件集合

// 定义父类事件类型
type ParentEvents = 'onRedraw' | 'headerClick';
// 子类事件类型:继承父类事件 + 新增自身事件
type ChildEvents = ParentEvents | 'click';

// 泛型EventEmitter类,约束事件类型为字符串枚举
class EventEmitter<Event extends string = never> {
  events: Event[];
  listeners: Map<Event, Function[]> = new Map();

  constructor(events: Event[]) {
    this.events = events;
  }

  on(event: Event, handler: Function): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event)!.push(handler);
  }

  emit(event: Event, ...args: any[]): void {
    this.listeners.get(event)?.forEach(handler => handler(...args));
  }
}

// Parent类:指定事件类型为ParentEvents
class Parent extends EventEmitter<ParentEvents> {
  constructor() {
    super(['onRedraw', 'headerClick']);
  }
}

// Child类:指定事件类型为包含父类事件的ChildEvents
class Child extends EventEmitter<ChildEvents> {
  constructor() {
    super(['onRedraw', 'headerClick', 'click']);
  }
}

// 测试使用
const childInstance = new Child();
childInstance.on('click', () => console.log('触发click事件'));
childInstance.on('onRedraw', () => console.log('触发onRedraw事件'));
childInstance.emit('headerClick');

// 类型校验:下面这行代码会报错('invalidEvent'不在ChildEvents中),实现类型安全
// childInstance.on('invalidEvent', () => {});

方式2:让父类支持泛型扩展(更优雅)

如果希望父类能直接被子类扩展事件,无需重复写父类的事件列表,可以让Parent类也支持泛型:

type ParentEvents = 'onRedraw' | 'headerClick';

class EventEmitter<Event extends string = never> {
  events: Event[];
  listeners: Map<Event, Function[]> = new Map();

  constructor(events: Event[]) {
    this.events = events;
  }

  on(event: Event, handler: Function): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event)!.push(handler);
  }

  emit(event: Event, ...args: any[]): void {
    this.listeners.get(event)?.forEach(handler => handler(...args));
  }
}

// Parent类接受额外事件的泛型参数,默认无额外事件
class Parent<AdditionalEvents extends string = never> 
  extends EventEmitter<ParentEvents | AdditionalEvents> {
  
  constructor(additionalEvents?: AdditionalEvents[]) {
    const parentEvents: ParentEvents[] = ['onRedraw', 'headerClick'];
    super([...parentEvents, ...(additionalEvents || [])]);
  }
}

// Child类仅需传入新增的事件类型即可
class Child extends Parent<'click'> {
  constructor() {
    super(['click']);
  }
}

// 测试使用
const childInstance = new Child();
childInstance.on('click', () => console.log('触发click事件'));
childInstance.on('headerClick', () => console.log('触发headerClick事件'));

关键说明

  1. 泛型约束解决类型冲突:通过泛型<Event extends string>,让每个类的事件类型都是明确的联合类型,子类的事件集合是父类的超集,符合TypeScript的类型兼容性规则。
  2. 类型安全:所有和事件相关的操作(on/emit、listeners键)都受泛型约束,避免传入非法事件名称,在编译阶段就能发现错误。
  3. 两种方式的适用场景:
    • 方式1适合需要完全自定义事件列表的场景,子类可以自由组合父类和自身的事件。
    • 方式2适合需要复用父类事件列表的场景,子类只需传入新增事件,无需重复声明父类已有的事件。

内容的提问来源于stack exchange,提问作者Przemysław Niedziela

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 23:22:50