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

TypeScript中如何让subscribe回调仅接收联合类型对应事件

TypeScript订阅函数类型不兼容问题解决

问题代码

以下是用于订阅特定事件类型的TypeScript代码,基于联合类型提取事件:

type Notification = {
    readonly type: "new_photo";
} | {
    readonly type: "liked_photo";
    readonly id: string
} | {
    readonly type: "disliked_photo";
    readonly id: string
}

type EventType = "new_photo" | "liked_photo" | "disliked_photo"

type Subscribers = {
  [K in EventType]?: Set<
    (message: Extract<Notification, { type: K }>) => void
  >;
};

const subscribers: Subscribers = {};

export function subscribe<T extends EventType>(
  type: T,
  callback: (message: Extract<Notification, { type: T }>) => void,
) {
  if (!subscribers[type]) {
    subscribers[type] = new Set(); // 此处出现类型错误
  }
  subscribers[type]?.add(callback);
  return () => {
    subscribers[type]?.delete(callback);
  };
}

初始化时的类型错误

上述代码中,初始化Set时出现以下类型错误:

Type 'Set<(message: Extract<{ readonly type: "new_photo"; }, { type: T; }> | Extract<{ readonly type: "liked_photo"; readonly id: string & Brand<"PhotoId">; }, { type: T; }> | ... 5 more ... | Extract<...>) => void>' is not assignable to type 'Subscribers[T]'.
Type 'Set<...>' is not assignable to type 'Set<(message: { readonly type: "new_photo"; }) => void> & Set<...>'.
...(省略后续错误详情)

临时修复后的新问题

尝试用new Set([])消除初始化错误后,调用回调时又出现新错误,提示参数应为never类型:

export function subscribe<T extends EventType>(
  type: T,
  callback: (message: Extract<Notification, { type: T }>) => void,
) {
  if (!subscribers[type]) {
    subscribers[type] = new Set([]); // 此处无错误
  }
  subscribers[type]?.add(callback);
  return () => {
    subscribers[type]?.delete(callback);
  };
}

function callSubscribers(event: Notification) {
  if (subscribers[event.type]) {
    subscribers[event.type]?.forEach((cb) => {
      cb(event); // 错误:参数应为never类型
    });
  }
}

解决方案

1. 修复初始化时的类型错误

在初始化Set时,使用类型断言明确告知TypeScript该集合的类型为对应事件的回调集合:

export function subscribe<T extends EventType>(
  type: T,
  callback: (message: Extract<Notification, { type: T }>) => void,
) {
  if (!subscribers[type]) {
    subscribers[type] = new Set() as Subscribers[T]; // 添加类型断言
  }
  subscribers[type]?.add(callback);
  return () => {
    subscribers[type]?.delete(callback);
  };
}

2. 修复回调调用时的类型错误

有两种常用方式解决此问题:

方式一:泛型收窄事件类型

重构callSubscribers函数,用泛型明确事件与回调的匹配关系:

function callSubscribers<K extends EventType>(event: Extract<Notification, { type: K }>) {
  const eventSubscribers = subscribers[event.type] as Subscribers[K];
  eventSubscribers?.forEach(cb => cb(event));
}

方式二:显式分支判断

通过switch分支让TypeScript自动收窄类型:

function callSubscribers(event: Notification) {
  switch (event.type) {
    case "new_photo":
      subscribers.new_photo?.forEach(cb => cb(event));
      break;
    case "liked_photo":
      subscribers.liked_photo?.forEach(cb => cb(event));
      break;
    case "disliked_photo":
      subscribers.disliked_photo?.forEach(cb => cb(event));
      break;
  }
}

原理说明

  • 初始化错误:TypeScript无法自动将泛型T对应的具体回调集合类型与new Set()的推断类型关联,类型断言直接明确了集合的正确类型。
  • 调用回调错误:subscribers[event.type]的类型是所有事件回调集合的联合,TypeScript无法自动推断当前event与回调参数的匹配关系,通过泛型收窄或显式分支可解决此问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 01:04:55