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

TypeScript如何从类型推导字面量值消除MessageHub冗余参数

核心结论

TypeScript 的所有类型信息仅存在于编译阶段,编译为 JS 后会被完全擦除,因此无法直接从泛型参数中提取运行时可用的name值,不过可以通过调整设计完全消除你提到的冗余问题,不用同时指定泛型和name参数。


方案1:利用自动类型推导(最轻量改造)

你当前的代码本身就支持 TS 自动推导泛型,根本不需要显式传入泛型参数,传入name后 TS 会自动匹配对应的消息类型,完全没有冗余:

interface Message {
  name: "ping" | "pong";
}

interface PingMessage extends Message {
  name: "ping";
  value: string;
}

interface PongMessage extends Message {
  name: "pong";
  value: string;
}

type HubMessage = PingMessage | PongMessage;
type MessageHandler<T extends HubMessage> = (message: T) => any | Promise<any>;

// 先定义消息名和消息类型的映射关系
type MessageTypeMap = {
  [K in HubMessage["name"]]: Extract<HubMessage, {name: K}>
};

class MessageHub {
  private recipients: {
    [K in keyof MessageTypeMap]?: MessageHandler<MessageTypeMap[K]>[]
  } = {};

  public receive<K extends keyof MessageTypeMap>(
    name: K,
    handler: MessageHandler<MessageTypeMap[K]>
  ) {
    this.recipients[name] = [...(this.recipients[name] ?? []), handler];
  }

  public send(message: HubMessage) {
    (this.recipients[message.name] ?? []).forEach((handler) =>
      handler(message)
    );
  }
}

const hub = new MessageHub();

// 无需显式传泛型,自动推导消息类型
hub.receive("ping", (m) => console.log(`pinging ${m.value}`)); // m 自动识别为 PingMessage 类型
hub.receive("ping", (m) => console.log(`pinging too ${m.value}`));
hub.receive("pong", (m) => console.log(`ponging ${m.value}`)); // m 自动识别为 PongMessage 类型

hub.send({ name: "ping", value: "fly there" });
hub.send({ name: "pong", value: "and back" });

方案2:完全去掉name参数

如果你希望注册监听时连字符串name都不用传,可以用类来定义消息类型,利用运行时存在的类构造函数来提取消息标识:

// 用类定义消息,运行时保留构造函数信息
class PingMessage {
  readonly name = "ping" as const;
  constructor(public value: string) {}
}

class PongMessage {
  readonly name = "pong" as const;
  constructor(public value: string) {}
}

type HubMessage = PingMessage | PongMessage;
type MessageHandler<T extends HubMessage> = (message: T) => any | Promise<any>;

class MessageHub {
  private recipients: Record<string, MessageHandler<any>[]> = {};

  public receive<T extends new (...args: any[]) => HubMessage>(
    messageCls: T,
    handler: MessageHandler<InstanceType<T>>
  ) {
    // 可以给消息类加静态name属性避免生成实例,这里是示例写法
    const msgName = new messageCls("").name;
    this.recipients[msgName] = [...(this.recipients[msgName] ?? []), handler];
  }

  public send(message: HubMessage) {
    (this.recipients[message.name] ?? []).forEach((handler) =>
      handler(message)
    );
  }
}

const hub = new MessageHub();
// 注册时仅需传消息类,无需传name也无需指定泛型
hub.receive(PingMessage, (m) => console.log(`pinging ${m.value}`));
hub.receive(PongMessage, (m) => console.log(`ponging ${m.value}`));

hub.send(new PingMessage("fly there"));
hub.send(new PongMessage("and back"));

内容的提问来源于stack exchange,提问作者Tadeáš Peták

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 00:57:04