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

TypeScript事件订阅器类型推断问题:函数参数错误为事件联合类型

问题描述

我正在实现eventSubscriber工具,功能如下:

  • 第一个参数是接收函数对象的回调,返回事件与处理函数的关联列表(支持Ctrl+点击跳转至函数实现);
  • 第二个参数是事件处理函数对象,期望处理函数的参数类型能自动从其关联的事件类(原型包含payload属性)推断而来。

但当前实现存在bug:当关联不同事件到不同处理函数时,所有处理函数的参数类型都会被推断为所有关联事件的联合类型,而非对应的单个事件。例如sendEmail关联UserRegisteredEvent,但其参数类型却被推断为UserRegisteredEvent | UserUnregisteredEvent。

当前的类型定义:

const eventSubscriber = <
  TFunctions extends { [key in string]: (event: any) => Promise<void> },
  TSubscriptions extends {
    event: { name: string; prototype: { payload: any } };
    function: TFunctions[keyof TFunctions];
  }
>(
  subscriptions: (functions: TFunctions) => TSubscriptions[],
  functions: {
    [key in keyof TFunctions]: (
      event: TSubscriptions["function"] extends TFunctions[key]
        ? TSubscriptions["event"] extends infer UEvent
          ? UEvent extends any
            ? UEvent
            : never
          : never
        : never,
    ) => Promise<void>;
  },
) => {
//...
};

我认为问题出在event: TSubscriptions["function"] extends TFunctions[key]这部分——由于所有Function类型都能互相extends,导致该条件恒为true,从而返回所有事件的联合类型。需要更严格的函数类型比较,或者其他可行解决方法。

解决方案

核心问题是TSubscriptions被推断为所有订阅项的联合类型,导致通过TSubscriptions["function"]和TSubscriptions["event"]取到的是所有函数和事件的联合。需要让TypeScript能为每个处理函数单独匹配对应的事件,而非统一处理联合类型。

修正后的类型定义如下:

const eventSubscriber = <
  TEvents extends { name: string; prototype: { payload: any } },
  TFuncMap extends Record<string, (event: any) => Promise<void>>,
  TSubs extends {
    [K in keyof TFuncMap]: { event: TEvents; function: TFuncMap[K] }
  }[keyof TFuncMap][]
>(
  subscriptions: (functions: TFuncMap) => TSubs,
  functions: {
    [K in keyof TFuncMap]: (
      event: Extract<TSubs[number], { function: TFuncMap[K] }>["event"]
    ) => Promise<void>;
  },
) => {
//...
};

关键改动说明

  1. 拆分泛型参数:将事件类型TEvents、函数映射TFuncMap和订阅列表TSubs分开定义,避免联合类型的混淆。
  2. 精准匹配事件:使用Extract<TSubs[number], { function: TFuncMap[K] }>["event"],为每个函数键K从订阅列表中提取仅匹配当前函数的事件类型,而非取所有事件的联合。
  3. 订阅列表类型约束:通过{ [K in keyof TFuncMap]: { event: TEvents; function: TFuncMap[K] } }[keyof TFuncMap][]确保每个订阅项的函数都属于TFuncMap,同时保留事件与函数的一对一关联。

效果验证

修正后,每个处理函数的参数类型会被精准推断为其关联的单个事件类型:

  • sendEmail的参数类型仅为UserRegisteredEvent
  • logUnregister的参数类型仅为UserUnregisteredEvent

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 16:17:48