React Reducer泛型改造:Records与可区分联合类型访问报错排查
解决React Reducer拆分后TypeScript类型不匹配问题
问题场景
为了让React Reducer更通用,将动作处理逻辑拆分为独立函数时,定义了可区分联合类型Actions:
export type Actions = | { type: "ADD_COLUMN"; newColumnIndex: number; column: SelectorColumnData; } | { type: "ITEM_CHECKED"; columnIndex: number; item: SelectorItem; }; export type ActionTypes = Actions["type"];
接着用Record创建动作处理函数映射:
const reducerRecord: Record<ActionTypes, (draft: Draft<SelectorState>, action: Actions) => void> = { "ITEM_CHECKED": itemCheckedAction, "ADD_COLUMN": addColumnAction, }; export const selectorReducer: ImmerReducer<SelectorState, Actions> = ( draft: Draft<SelectorState>, action: Actions, ): void => { reducerRecord[action.type](draft, action); };
但编写itemCheckedAction时,TypeScript报错TS2339: property 'columnIndex' does not exist on type 'Actions',尝试用Extract断言或类型守卫都无法解决问题。
解决方案
核心问题是每个动作处理函数应该接收对应具体类型的Action,而非整个联合类型,同时调整映射对象的类型定义以确保类型安全。
1. 给处理函数指定精确的Action类型
直接为每个处理函数的action参数指定对应的子类型,而非Actions联合类型:
// 处理ITEM_CHECKED动作的函数,仅接收对应类型的action export const itemCheckedAction = ( draft: Draft<SelectorState>, action: Extract<Actions, { type: "ITEM_CHECKED" }> ) => { const { columnIndex, item } = action; // 此处无类型错误 // 业务逻辑处理 }; // 处理ADD_COLUMN动作的函数,仅接收对应类型的action export const addColumnAction = ( draft: Draft<SelectorState>, action: Extract<Actions, { type: "ADD_COLUMN" }> ) => { const { newColumnIndex, column } = action; // 此处无类型错误 // 业务逻辑处理 };
2. 调整映射对象的类型定义
使用映射类型替代Record,让TypeScript自动关联Action类型与对应的处理函数参数类型:
// 定义映射类型,确保每个Action类型对应正确的处理函数签名 type ReducerHandlers = { [T in ActionTypes]: ( draft: Draft<SelectorState>, action: Extract<Actions, { type: T }> ) => void; }; // 初始化映射对象,TypeScript会严格校验每个处理函数的类型 const reducerRecord: ReducerHandlers = { "ITEM_CHECKED": itemCheckedAction, "ADD_COLUMN": addColumnAction, }; // 最终Reducer函数 export const selectorReducer: ImmerReducer<SelectorState, Actions> = ( draft: Draft<SelectorState>, action: Actions ): void => { // 此处需要类型断言,因为TypeScript暂时无法自动推断action.type与handler的关联 (reducerRecord[action.type as ActionTypes] as (draft: Draft<SelectorState>, action: Actions) => void)(draft, action); };
补充:修复类型守卫的写法
如果坚持在处理函数中使用联合类型参数,需去掉变量的类型注解,让TypeScript自动推断守卫后的类型:
export const itemCheckedAction = (draft: Draft<SelectorState>, action: Actions) => { if(action.type === "ITEM_CHECKED") { const { columnIndex, item } = action; // TypeScript自动推断为ITEM_CHECKED类型 } };
内容的提问来源于stack exchange,提问作者serlingpa
相关产品推荐
相关产品推荐

