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

使用TypeScript区分回调联合类型的实现问题

如何在TypeScript中实现带类型区分的链式on()事件接口

你遇到的这个问题其实是TypeScript里处理不同事件对应不同回调类型的典型场景,完全可以实现你想要的链式on()接口——核心是让TypeScript能根据你传入的事件类型,自动推断出对应的回调参数类型,而不是用一个宽泛的联合类型来兜底。下面给你两种可行的解决方案:

方案一:使用函数重载(适合简单场景)

函数重载是TypeScript专门用来处理“同一函数接受不同参数类型,对应不同行为”的特性。我们可以给on方法定义多个重载签名,每个签名对应一个事件类型和它专属的回调类型,这样TypeScript在调用时就能自动校验回调的参数类型:

interface Content { x: string, y: string }
type DataCallback = (content: string) => void;
type ErrorCallback = (content: string) => void;
type FinishedCallback = (content: Content) => void;

class Handler {
  // 先声明每个事件对应的回调存储属性(建议加可选符,避免未初始化报错)
  ondata?: DataCallback;
  onerror?: ErrorCallback;
  onfinished?: FinishedCallback;

  // 重载签名:每个事件对应正确的回调类型
  on(event: "data", callback: DataCallback): this;
  on(event: "error", callback: ErrorCallback): this;
  on(event: "finished", callback: FinishedCallback): this;
  
  // 实现签名:兼容所有重载情况,这里用类型断言处理赋值(因为重载已经保证了类型匹配)
  on(event: string, callback: DataCallback | ErrorCallback | FinishedCallback): this {
    switch(event) {
      case "data":
        this.ondata = callback as DataCallback;
        break;
      case "error":
        this.onerror = callback as ErrorCallback;
        break;
      case "finished":
        this.onfinished = callback as FinishedCallback;
        break;
      default:
        console.error(`Unhandled event: ${event}`);
    }
    return this;
  }
}

// 现在使用时会自动校验回调类型,还能获得类型提示
const handler = new Handler()
  .on("data", (content) => {
    // content 被自动推断为 string
    console.log(content.toUpperCase());
  })
  .on("finished", (content) => {
    // content 被自动推断为 Content
    console.log(content.x, content.y);
  });

为什么之前的代码会报错?

你之前用EventCallback联合类型的问题在于:TypeScript无法确定你传入的callback到底是哪一种类型——比如当你传入event="data"时,callback可能是FinishedCallback(因为联合类型包含它),而FinishedCallback的参数是Content,和DataCallback的string参数不兼容,所以TypeScript会抛出错误。而重载机制在调用阶段就锁定了事件和回调的对应关系,从根源上避免了这种类型不匹配的问题。

方案二:泛型+映射类型(适合需扩展的场景)

如果以后需要添加更多事件类型,重载的方式会需要不断新增重载签名,比较繁琐。这时候可以用映射类型定义事件和回调的映射关系,再结合泛型实现更灵活的on方法:

interface Content { x: string, y: string }

// 定义事件与回调的映射表,新增事件只需要在这里添加
type EventMap = {
  data: (content: string) => void;
  error: (content: string) => void;
  finished: (content: Content) => void;
};

// 自动推导所有事件类型
type EventType = keyof EventMap;

class Handler {
  // 用对象存储所有回调,Partial表示所有属性可选
  private callbacks: Partial<Record<EventType, EventMap[EventType]>> = {};

  // 泛型方法:根据传入的事件类型E,自动推断对应的回调类型EventMap[E]
  on<E extends EventType>(event: E, callback: EventMap[E]): this {
    this.callbacks[event] = callback;
    return this;
  }

  // 额外:如果需要触发事件,也能利用泛型自动校验参数类型
  trigger<E extends EventType>(event: E, ...args: Parameters<EventMap[E]>) {
    const callback = this.callbacks[event];
    if (callback) {
      callback(...args);
    }
  }
}

// 使用示例
const handler = new Handler()
  .on("data", (content) => console.log(content)) // content: string
  .on("finished", (content) => console.log(content.x)); // content: Content

// 触发事件时也会自动校验参数
handler.trigger("data", "hello world"); // 正确
handler.trigger("finished", {x: "foo", y: "bar"}); // 正确
// handler.trigger("finished", "错误参数"); // 这里会直接报错,类型不匹配

这种方式的优势在于扩展性极强:以后新增事件,只需要在EventMap里添加对应的键值对即可,不需要修改on或trigger方法的代码,TypeScript会自动推导所有类型。

总结

两种方案都能完美保留你想要的链式on()接口形式:

  • 如果事件类型固定且较少,用函数重载更直观;
  • 如果需要频繁新增事件,用泛型+映射类型更灵活。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 19:32:28