如何从映射对象推导Action类型以实现useReducer的dispatch类型安全?
基于Reducer动作映射自动推导Action类型
前言
我通常会采用以下方式定义reducer,以此保证dispatch方法的类型安全:
const setWhatever = (state: State, payload: Partial<State>) => ({ ...state, ...payload }); // 动作类型与处理函数的映射表 const actions = { SET_WHATEVER: setWhatever, } type Action = | { type: 'SET_WHATEVER', payload: Parameters<typeof setWhatever>[1] } const reducer = (state: State, action: Action) => actions[action.type](state, action.payload); const [state, dispatch] = useReducer(reducer, {});
这里type Action的定义重复且繁琐,能否基于const actions自动推导出type Action?
当前v1解决方案
我已经实现了一个基本可用的CreateActions<T>映射类型:
type CreateActions<T extends typeof actions> = { [K in keyof T]: { type: K; payload: T[K] extends (...args: any[]) => any ? Parameters<T[K]>[1] : never; }; }[keyof T]; type Action = CreateActions<typeof actions>;
但该方案仍存在两个问题:
reducer需要额外添加as never类型断言才能消除lint错误:
/** * 下方的`as never`用于解决如下类型错误: * ``` * Argument of type 'string | number | Partial<{ name: string; age: number; }> | undefined' is not assignable to parameter of type 'never'. * Type 'undefined' is not assignable to type 'never'.ts(2345) * ``` * * 问题:有没有更好的解决方式? */ const reducer = (state: State, action: Action): State => actions[action.type](state, action.payload as never);
- 所有
dispatch调用都必须携带payload,能否让部分动作无需传入payload?
dispatch({ type: 'RESET', payload: undefined }); // 若写成dispatch({ type: 'RESET' })会触发lint报错
当前v2解决方案
感谢@ryskajakub提供的最终方案:
type Payload<T> = T extends (...args: any[]) => any ? Parameters<T>[1] : never; type CreateActions<T extends typeof actions> = { [K in keyof T]: Payload<T[K]> extends undefined ? { type: K; // 定义可选payload避免reducer报错 payload?: undefined; } : { type: K; payload: Payload<T[K]>; }; }[keyof T]; /** 鼠标悬停在Action上可查看判别联合类型 */ type Action = CreateActions<typeof actions>; const reducer: ActionHandler<Action> = (state, action): State => (actions[action.type] as ActionHandler<typeof action["payload"]>)( state, action.payload );
现在我可以无类型错误地调用dispatch({ type: 'RESET' })。
一个小问题是调用dispatch({ type: 'RESET', payload: undefined })也不会触发类型错误,但我对此并不在意。
内容的提问来源于Stack Exchange,提问作者David Horm
相关产品推荐
相关产品推荐

