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

如何在TypeScript中无类型断言合并EventHandlerMap类型?

解决TypeScript中EventHandlerMap合并的类型兼容性问题

我需要实现一个composeMaps函数,将两个EventHandlerMap对象合并为对应联合事件类型的EventHandlerMap,但TypeScript编译器报错:

类型“EventHandlerMap & EventHandlerMap”无法赋值给类型“EventHandlerMap<E1 | E2>”。(2322)

原类型定义与尝试代码如下:

type Event<T extends string = string, D = unknown> = {
  type: T;
  data: D;
}

type EventHandlerMap<E extends Event> = {
  [Ev in E as Ev['type']]: (data: Ev['data']) => void
}

function composeMaps<E1 extends Event, E2 extends Event>(
  eventHandlerMap1: EventHandlerMap<E1>,
  eventHandlerMap2: EventHandlerMap<E2>,
): EventHandlerMap<E1 | E2> {
  return {
    ...eventHandlerMap1,
    ...eventHandlerMap2,
  }
}

原因分析

编译器报错的核心是:EventHandlerMap<E1> & EventHandlerMap<E2>(两个映射的交叉类型)和EventHandlerMap<E1 | E2>并不总是类型兼容:

  • 当两个事件集合的type不重叠时,交叉类型等价于目标类型;
  • 当存在相同type的事件时,交叉类型中对应的处理函数要求参数同时满足两个原函数的参数类型(即参数类型为交集),而目标类型要求参数支持两个事件的联合数据类型,此时类型约束方向相反,导致不兼容。

解决方案1:约束事件类型不重叠

如果业务场景中两个输入的EventHandlerMap不会处理相同type的事件,可以给泛型添加约束,确保E1和E2的事件类型完全不重叠:

type Event<T extends string = string, D = unknown> = {
  type: T;
  data: D;
}

type EventHandlerMap<E extends Event> = {
  [Ev in E as Ev['type']]: (data: Ev['data']) => void
}

// 添加约束:E2的type不在E1的type范围内
function composeMaps<
  E1 extends Event, 
  E2 extends Event & { type: Exclude<E2['type'], E1['type']> }
>(
  eventHandlerMap1: EventHandlerMap<E1>,
  eventHandlerMap2: EventHandlerMap<E2>,
): EventHandlerMap<E1 | E2> {
  return {
    ...eventHandlerMap1,
    ...eventHandlerMap2,
  }
}

解决方案2:允许重叠事件但统一处理函数类型

如果需要支持相同type的事件合并,需要确保相同type对应的处理函数参数能兼容联合数据类型。可以通过修改EventHandlerMap的定义,让编译器认可兼容性:

type Event<T extends string = string, D = unknown> = {
  type: T;
  data: D;
}

// 重新定义EventHandlerMap,明确对联合事件类型的处理逻辑
type EventHandlerMap<E extends Event> = {
  [K in Extract<E, Event>['type']]: 
    (data: Extract<E, { type: K }>['data']) => void
}

// 辅助类型:将交叉类型展开为普通对象类型
type MergeHandlerMaps<M1, M2> = {
  [K in keyof (M1 & M2)]: (M1 & M2)[K]
}

function composeMaps<E1 extends Event, E2 extends Event>(
  eventHandlerMap1: EventHandlerMap<E1>,
  eventHandlerMap2: EventHandlerMap<E2>,
): MergeHandlerMaps<EventHandlerMap<E1>, EventHandlerMap<E2>> {
  return {
    ...eventHandlerMap1,
    ...eventHandlerMap2,
  }
}

MergeHandlerMaps展开后的类型等价于EventHandlerMap<E1 | E2>(当相同type的处理函数参数兼容时)。

解决方案3:直接返回交叉类型

实际使用中,交叉类型的对象完全可以当作EventHandlerMap<E1 | E2>来使用(只要处理函数参数足够宽泛),这种方式最简单:

type Event<T extends string = string, D = unknown> = {
  type: T;
  data: D;
}

type EventHandlerMap<E extends Event> = {
  [Ev in E as Ev['type']]: (data: Ev['data']) => void
}

function composeMaps<E1 extends Event, E2 extends Event>(
  eventHandlerMap1: EventHandlerMap<E1>,
  eventHandlerMap2: EventHandlerMap<E2>,
): EventHandlerMap<E1> & EventHandlerMap<E2> {
  return {
    ...eventHandlerMap1,
    ...eventHandlerMap2,
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 11:03:27