聊天讨论 TypeScript 高级类型:我用 infer 写了一个类型安全的 EventBus,终于搞懂了泛型约束

193577746(kyriewen) · June 16, 2026 · 12 hits

TypeScript 写了五年,any 也用了五半。直到被迫写一个类型安全的 EventBus,我才真正搞懂 infer、extends、keyof 和泛型约束。本文从实际场景出发,一步步推导出类型安全的 API。

一、痛点:EventBus 的类型安全问题

写一个简单的 EventBus:

class EventBus {
  private listeners: Record<string, Function[]> = {};

  on(event: string, callback: Function) {
    if (!this.listeners[event]) this.listeners[event] = [];
    this.listeners[event].push(callback);
  }

  emit(event: string, ...args: any[]) {
    (this.listeners[event] || []).forEach(cb => cb(...args));
  }
}

问题on('userLogin', (name: string) => {})emit('userLogin', 123) 类型不匹配,但 TS 不会报错。any[] 毁掉了所有类型安全。

目标:让 onemit 的事件名和参数类型自动关联。

二、核心工具:泛型 + 映射类型

2.1 定义事件映射

interface EventMap {
  userLogin: (name: string, age: number) => void;
  userLogout: () => void;
  dataUpdate: (data: { id: number }) => void;
}

2.2 提取参数类型(infer 闪亮登场)

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

infer P 的意思是 “在符合这个形状的地方,把参数类型推断出来赋值给 P”。

2.3 实现类型安全的 EventBus

class TypedEventBus<T extends Record<string, (...args: any[]) => any>> {
  private listeners: {
    [K in keyof T]?: T[K][];
  } = {};

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

  emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>) {
    (this.listeners[event] || []).forEach(cb => cb(...args));
  }
}

2.4 使用效果

const bus = new TypedEventBus<EventMap>();

bus.on('userLogin', (name, age) => {
  console.log(name, age); // name: string, age: number
});

bus.emit('userLogin', '张三', 25); // ✅ 类型正确
bus.emit('userLogin', 123);        // ❌ TS 报错:参数类型不匹配
bus.emit('userLogout');            // ✅ 无参数

三、5 个最实用的高级类型技巧

技巧 1:keyof 提取对象的键

type Keys = keyof { name: string; age: number }; // 'name' | 'age'

技巧 2:typeof 获取值的类型

const config = { host: 'localhost', port: 3000 };
type Config = typeof config; // { host: string; port: number }

技巧 3:infer 提取函数返回类型

type ReturnType<T> = T extends (...args: any) => infer R ? R : never;

技巧 4:extends 约束泛型

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

技巧 5:in 映射类型

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

四、完整 EventBus 代码(可直接复制)

// 类型安全的 EventBus
interface EventMap {
  [event: string]: (...args: any[]) => void;
}

class TypedEventBus<T extends EventMap> {
  private listeners: {
    [K in keyof T]?: T[K][];
  } = {};

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

  off<K extends keyof T>(event: K, callback: T[K]) {
    const callbacks = this.listeners[event];
    if (!callbacks) return;
    const index = callbacks.indexOf(callback);
    if (index !== -1) callbacks.splice(index, 1);
  }

  emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>) {
    const callbacks = this.listeners[event];
    if (!callbacks) return;
    callbacks.forEach(cb => cb(...args));
  }

  once<K extends keyof T>(event: K, callback: T[K]) {
    const wrapper = (...args: Parameters<T[K]>) => {
      callback(...args);
      this.off(event, wrapper as T[K]);
    };
    this.on(event, wrapper as T[K]);
  }
}

五、总结

  • infer 是高级类型中最强大的工具,用于在条件类型中提取类型信息
  • keyof + extends 可以实现类型安全的键值访问
  • typeof 可以将 JS 值转为 TS 类型
  • EventBus 的类型安全实现完整展示了这些工具的配合使用
No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.