能否将Map的键类型与其对应值类型建立关联?
能否将Map的键类型与其对应值类型建立关联?
当然可以啦!你遇到的正是TypeScript中实现类型安全的带关联键值对的Map的典型场景——默认的Map<K, V>泛型只能指定单一的值类型,没法让不同键对应不同的值类型,但我们可以通过类型工具和断言来解决这个问题。
先帮你梳理下原代码里的核心问题:你定义了ListenersMap这个映射类型,想让每个事件类型对应专属的回调集合,但直接用new Map()初始化时,TypeScript无法自动把Map和你的映射类型关联起来,所以会报类型不匹配的错误。
下面是修正后的完整代码,我会逐一解释关键改动:
type Events = { type: 'e1'; payload: string } | { type: 'e2'; payload: number }; type AddListener = <const EventName extends Events['type']>( eventName: EventName, cb: (event: Extract<Events, { type: EventName }>) => void, ) => () => void; const addListener: AddListener = (eventName, cb) => { // 1. 定义每个事件类型对应的回调集合类型 type ListenersMap = { [K in Events['type']]: Set<(event: Extract<Events, { type: K }>) => void>; }; // 2. 定义增强版的TypedMap工具类型,让Map的get/set方法能关联键值类型 type TypedMap<T> = Map<keyof T, T[keyof T]> & { get<K extends keyof T>(key: K): T[K] | undefined; set<K extends keyof T>(key: K, value: T[K]): this; }; // 3. 用断言把普通Map转为我们的增强类型 const listenersMap: TypedMap<ListenersMap> = new Map() as TypedMap<ListenersMap>; // 初始化:如果事件类型对应的集合不存在,就创建新Set if (!listenersMap.get(eventName)) { listenersMap.set(eventName, new Set()); } // 将当前回调添加到对应集合 listenersMap.get(eventName)!.add(cb); return function removeEventListener() { // 移除回调的逻辑:找到对应集合删除回调,空集合可清理键 const eventListeners = listenersMap.get(eventName); if (eventListeners) { eventListeners.delete(cb); if (eventListeners.size === 0) { listenersMap.delete(eventName); } } }; };
关键改动说明:
ListenersMap映射类型:明确了每个事件类型(比如e1/e2)对应的回调集合类型,例如e1对应Set<(event: { type: 'e1'; payload: string }) => void>,e2对应Set<(event: { type: 'e2'; payload: number }) => void>,从根源上定义键值的类型关联。TypedMap工具类型:- 继承了原生
Map的基础功能 - 重写了
get和set方法的类型签名,让TypeScript知道:传入某个键类型K,就必须返回/传入ListenersMap[K]类型的值,完美建立键值类型的强关联。
- 继承了原生
类型断言:因为原生
Map的构造函数无法直接生成我们的增强类型,所以用as TypedMap<ListenersMap>做一次安全的类型断言——只要后续操作都遵循类型约定,就不会有类型安全问题。
这样修改后,你不仅能实现键值类型的强关联,后续操作(比如添加/移除回调)也都会享受到TypeScript的类型检查:比如你不小心给e1的集合添加e2的回调,TypeScript会直接报错,帮你提前规避bug~
备注:内容来源于stack exchange,提问作者aryzing
相关产品推荐
相关产品推荐

