TypeScript泛型报错EventHandler<T[K]>不可赋值给T[keyof T]原因求解
类型报错原因
你遇到的报错核心是TypeScript函数参数的逆变特性导致的,和K是不是keyof T的子集没有直接关系:
- 你定义的
eventsMap的value类型是EventHandler<T[keyof T]>,这个类型表示传入的函数需要可以处理T所有事件的任意参数(T[keyof T]是所有事件参数类型的联合) - 而
on方法里传入的handler是EventHandler<T[K]>,这个类型表示函数只能处理K对应事件的单一参数类型
函数参数属于逆变位置,赋值时要求目标参数类型是源参数类型的子集,这里刚好反过来,自然会报类型不匹配。
举个直观例子:假设你的T定义为{ test: [string], count: [number] },那么T[keyof T]是[string] | [number],EventHandler<T[keyof T]>要求函数必须能同时接收字符串和数字参数,但你传入的EventHandler<T['test']>只能接收字符串参数,赋值的话就会存在类型安全风险,TS因此阻止了这个赋值操作。
修复方案
推荐两种生产环境常用的方案,都不会影响对外API的类型安全:
方案1:内部存储使用any类型
对外暴露的on、emit方法保留强类型校验,内部存储用any规避类型推导问题,代码最简单:
export type EventType = string | symbol; export type Events = Record<EventType, unknown[]>; export type EventHandler<T = unknown[]> = (args: T) => void; export class EventEmitter<T extends Events> { // 内部用any存储,不影响外部使用的类型安全 private events = new Map<keyof T, EventHandler<any>>(); public on<K extends keyof T>(key: K, handler: EventHandler<T[K]>): void { this.events.set(key, handler); } public emit<K extends keyof T>(key: K, args: T[K]): void { const handler = this.events.get(key); if (handler) handler(args); } // 其他off、once等方法同理做类型约束即可 }
方案2:使用类型断言
如果你不想修改原有的Map类型定义,直接在赋值时加类型断言即可,逻辑上是安全的,因为你可以保证同一个key对应的handler一定是对应参数类型的:
export class EventEmitter<T extends Events> { private events = new Map<keyof T, EventHandler<T[keyof T]>>(); public on<K extends keyof T>(key: K, handler: EventHandler<T[K]>) { // 类型断言告诉TS这个赋值是符合预期的 this.events.set(key, handler as EventHandler<T[keyof T]>); } }
内容的提问来源于stack exchange,提问作者Galwet
相关产品推荐
相关产品推荐

