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

TypeScript中解决SafeEmitter封装EventEmitter的类型报错问题

Hey there! Let's fix those TypeScript type errors with your SafeEmitter class step by step—no type assertions needed. Here's how to get it working properly:

First, Let's Address the Key Type Issue

The problem where keyof Events was inferring a number type happens because TypeScript allows numeric keys in objects by default. If your Events type included numeric keys (or wasn't constrained properly), keyof Events would include number alongside string/symbol, causing a mismatch with the expected channel type. To fix this, we need to explicitly constrain the Events generic to only use string or symbol as keys, with values being tuples that define each event's parameters.

Then, Fix the Listener Parameter Mismatch

The second error with listener compatibility comes from not tying the listener's arguments directly to the event's defined parameter tuple. Instead of using any[], we need to make the listener's arguments match exactly what the event expects.

The Corrected SafeEmitter Implementation

Here's the full, type-safe implementation without any assertions:

class SafeEmitter<Events extends Record<string | symbol, any[]>> {
  // Use a mapped type to ensure listeners match each event's parameter tuple
  private listeners: { [K in keyof Events]?: ((...args: Events[K]) => void)[] } = {};

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

  emit<K extends keyof Events>(channel: K, ...args: Events[K]): void {
    this.listeners[channel]?.forEach(listener => listener(...args));
  }
}

How This Solves Your Issues

  1. Key Type Constraint: By using Events extends Record<string | symbol, any[]>, we ensure all keys in your Events type are either string or symbol—so keyof Events will never include number, eliminating the channel type mismatch.
  2. Listener Type Safety: The listener is typed as (...args: Events[K]) => void, which means it must accept exactly the parameters defined for the specific event K. No more mismatches with any[] because we're strictly enforcing the parameter tuple from your Events interface.

Example Usage

Let's test this with a concrete event interface to see the type safety in action:

// Define your events as an interface where each key is an event name,
// and the value is a tuple of the event's parameters
interface AppEvents {
  'user-login': [username: string, loginTimestamp: Date];
  'message-sent': [content: string];
}

const emitter = new SafeEmitter<AppEvents>();

// This works perfectly—TypeScript knows the listener needs a string and Date
emitter.on('user-login', (username, time) => {
  console.log(`${username} logged in at ${time.toLocaleTimeString()}`);
});

// Emit with the correct parameters
emitter.emit('user-login', 'jane_smith', new Date());

// These will throw TypeScript errors (as expected, for type safety):
// emitter.emit('user-login', 'jane_smith'); // Missing the Date parameter
// emitter.on('unknown-event', () => {}); // "unknown-event" isn't in AppEvents
// emitter.on('message-sent', (content, extra) => {}); // Too many parameters

TSConfig Notes

Make sure your tsconfig.json has strict mode enabled to get the full benefit of these type checks. At minimum, ensure these settings are present:

{
  "compilerOptions": {
    "strict": true,
    "target": "ESNext",
    "moduleResolution": "NodeNext"
  }
}

This ensures TypeScript enforces the strict type constraints we've set up, preventing accidental type mismatches.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:01:54