如何用TypeScript类型确保Redux Reducer处理所有Action并推断精确类型
解决Redux Reducer映射中Action精确类型推断问题
我完全懂你碰到的这个痛点——你的IActionReducerMapping确实能保证覆盖所有指定的Action类型,但每个reducer里的action参数始终是联合类型,没法直接访问具体Action的专属属性,比如ADD_USER里的payload.age。这是因为原来的类型定义里,每个reducer的action都被绑定成了整个联合类型A,而不是对应type的那个具体Action。
解决方案:重构映射类型,实现精确类型推断
我们可以利用TypeScript的映射类型和条件类型,让每个Action类型对应的reducer,自动推断出它该处理的具体Action类型。修改后的类型定义如下:
export interface IReduxAction<T> { type: T; } interface IReducer<S, A> { (state: S, action: A): S; } // 新的映射类型:为每个Action类型匹配对应的具体Action export type IActionReducerMapping<S, A extends IReduxAction<string>> = { [T in A['type']]: IReducer<S, Extract<A, { type: T }>>; };
这里的关键是Extract<A, { type: T }>——它会从联合类型A中精准提取出type属性等于T的那个Action类型。这样一来,每个键(也就是Action的type值)对应的reducer,其action参数就不再是模糊的联合类型,而是该type对应的具体Action。
用你的例子验证效果
把这个新类型用到你的代码里,就能看到action被精确推断了:
interface IUser { id: number; name: string; age: number; } interface IUserState { [id: number]: IUser; } interface IAddUserAction { type: 'ADD_USER'; payload: IUser; } interface ISetUserNameAction { type: 'SET_USER_NAME'; payload: { id: IUser['id']; name: IUser['name']; } } type UserAction = IAddUserAction | ISetUserNameAction; const mapping: IActionReducerMapping<IUserState, UserAction> = { 'ADD_USER': (state, action) => { // 这里action被精确推断为IAddUserAction,直接访问payload.age毫无问题 console.log(action.payload.age); return { ...state, [action.payload.id]: action.payload }; }, 'SET_USER_NAME': (state, action) => { // 这里action被精确推断为ISetUserNameAction return { ...state, [action.payload.id]: { ...state[action.payload.id], name: action.payload.name } }; }, };
额外优化:封装创建Reducer的工具函数
为了让整个流程更丝滑,你可以封装一个工具函数,把初始状态和映射转换成标准的Redux reducer:
function createReducer<S, A extends IReduxAction<string>>( initialState: S, mapping: IActionReducerMapping<S, A> ): (state: S | undefined, action: A) => S { return (state = initialState, action) => { // 这里的类型断言是安全的,因为映射已经覆盖了所有Action类型 return mapping[action.type](state, action as Extract<A, { type: typeof action.type }>); }; } // 使用示例 const userReducer = createReducer<IUserState, UserAction>({}, mapping);
这样一来,你既保留了“必须覆盖所有Action类型”的强类型校验,又解决了Action类型不精确的问题,写reducer的时候再也不用手动做类型断言了。
内容的提问来源于stack exchange,提问作者OliverRadini
相关产品推荐
相关产品推荐

