TypeScript可辨识联合类型查找对象的正确类型定义方法
TypeScript 可辨识联合事件查找表类型报错解决方案
基础定义
首先明确基础事件类型与联合类型定义:
interface EventTypeBase { id: string; timestamp: string; } interface FizzChange extends EventTypeBase { name: 'fizz'; payload: string; } interface BuzzChange extends EventTypeBase { name: 'buzz'; payload: number; } // 所有事件的联合类型 type EventType = FizzChange | BuzzChange;
使用if语句做类型收窄时,TS可以正确推导分支内的类型,写法可正常运行:
const mapEventsWithIfs = (event: EventType) => { if (event.name === 'fizz') { return event.payload; // 自动推导为string类型 } if (event.name === 'buzz') { return event.payload; // 自动推导为number类型 } return; }
报错原因
使用事件名查找对象绑定处理函数时,直接调用会触发类型错误,提示参数被推导为never:
// 报错示例 const mapEvents = (event: EventType) => { // 报错:Argument of type 'EventType' is not assignable to parameter of type 'never' return EventHandlersMap[event.name](event); }
报错核心原因是:TS默认对索引取到的函数做类型合并时,会将所有处理函数的参数类型做交叉运算,要求传入的参数同时满足所有事件类型的约束。但FizzChange和BuzzChange的name属性是互斥的字面量类型,交叉结果为never,因此传入联合类型的event会触发类型错误。
正确实现方案
通过映射类型+泛型约束建立事件名与事件类型的绑定关系,即可让TS正确推导类型,无需任何不安全的类型断言:
- 首先定义严格类型约束的事件处理器映射表,通过映射类型自动关联每个事件名对应的事件类型:
const EventHandlersMap: { [K in EventType['name']]: (event: Extract<EventType, { name: K }>) => string | number | void } = { fizz: (event) => event.payload, // event自动推导为FizzChange,payload为string类型 buzz: (event) => event.payload, // event自动推导为BuzzChange,payload为number类型 }
- 将事件处理函数改为泛型函数,让TS自动关联传入事件的name类型与对应处理器的参数类型:
const mapEvents = <T extends EventType['name']>(event: Extract<EventType, { name: T }>) => { return EventHandlersMap[event.name](event); }
该写法完全通过类型校验,同时保留完整的类型提示:
- 编写处理器时可以自动推导对应事件的属性类型
- 后续新增事件类型时,TS会强制要求映射表补充对应的处理函数,避免漏判
- 调用mapEvents时传入任意EventType类型的事件,都可以正确推导返回值类型
如果不想封装泛型函数,也可以在调用时对event做临时类型断言,但该写法会丢失部分类型安全性,不推荐使用:
const mapEvents = (event: EventType) => { return EventHandlersMap[event.name](event as never); }
内容的提问来源于stack exchange,提问作者serik bu
相关产品推荐
相关产品推荐

