如何基于联合类型参数定义泛型并实现类型安全的动作处理函数
问题描述
我们有如下类型定义:
class ActionFoo { action: 'foo'; foo: string; } class ActionBar { action: 'bar'; bar: number; }
同时对应每个action字段有专属处理函数:
function fooHandler(action: ActionFoo) { // 处理逻辑 } function barHandler(action: ActionBar) { // 处理逻辑 }
需求是编写一个工具函数统一处理这些动作场景,但当前实现存在类型推断错误:
type ActionHandler<T extends { action: string }, O = any> = (action: T) => O; function handleAction<T extends { action: string }, O = any>( action: T, handlers: {[key in T['action']]: ActionHandler<T, O>} ) { const handler = handlers[action.action]; if (handler) return handler(action); throw new Error('not implemented'); } type Action = ActionFoo | ActionBar; function run(action: Action) { return handleAction(action, { foo: action => fooHandler(action), // TS报错:缺少`foo`字段(TS2345) bar: action => barHandler(action), // 类似类型错误 }); }
请问如何基于传入的联合类型参数定义泛型,修复上述类型错误?
解决方案
原代码的问题在于泛型T被推断为整个联合类型ActionFoo | ActionBar,导致handlers的每个属性都要求接收联合类型参数,但实际每个handler只接受对应具体类型。我们需要让泛型针对联合类型中的每个成员单独约束,而非绑定整个联合。
修正后的完整代码如下:
// 定义动作类型的基础约束 type BaseAction = { action: string }; // 根据动作联合类型,映射出每个action对应的handler类型 type ActionHandlers<A extends BaseAction, O = any> = { [K in A['action']]: (action: Extract<A, { action: K }>) => O; }; function handleAction<A extends BaseAction, O = any>( action: A, handlers: ActionHandlers<A, O> ): O { const handler = handlers[action.action as A['action']]; if (handler) { // 类型断言确保编译器识别action与handler的匹配关系 return handler(action as Extract<A, { action: typeof action.action }>); } throw new Error('not implemented'); } type Action = ActionFoo | ActionBar; function run(action: Action) { return handleAction(action, { foo: (action) => fooHandler(action), // 正确推断action为ActionFoo bar: (action) => barHandler(action), // 正确推断action为ActionBar }); }
关键改动说明
- 精准的handler类型映射:使用
Extract<A, { action: K }>从联合类型A中提取出action字段等于K的具体子类型,确保每个handler的参数是对应单个动作类型,而非整个联合。 - 泛型约束调整:
handleAction的泛型A约束为BaseAction,覆盖所有带action字段的类型,配合ActionHandlers实现精准的类型映射。 - 安全的类型断言:由于TypeScript对联合类型的自动窄化存在局限性,这里的
as断言是安全的——因为handlers的结构已经保证了每个action对应正确的handler,不会出现类型不匹配的情况。
你也可以利用TypeScript的分布式条件类型进一步简化handler的类型定义:
type ActionHandler<A> = A extends { action: infer K } ? (action: A) => any : never; type ActionHandlers<A extends BaseAction> = { [K in A['action']]: ActionHandler<Extract<A, { action: K }>>; }; // handleAction函数实现保持不变
修改后,每个handler的参数类型都会被正确推断为对应的具体动作类型,原代码中的类型错误即可解决。
内容的提问来源于stack exchange,提问作者Ghostoy
相关产品推荐
相关产品推荐

