如何在Redux中无需显式转换实现TypeScript枚举类型收窄?
好问题!你现在的写法确实有点繁琐,我们可以通过TypeScript的类型推断特性来去掉那些重复的显式类型转换,同时保持Reducer里的类型安全。下面有几种不同的实现方式,你可以根据自己的偏好选择:
方法1:改用函数式Action Creators(推荐)
如果不需要用类来组织Action creators,这种方式最简洁,完全不需要类型断言:
export enum ActionTypes { INCREMENT_BY_ONE = 'INCREMENT_BY_ONE', INCREMENT_BY_MULTIPLE = 'INCREMENT_BY_MULTIPLE', } // 每个Action creator都是独立函数,TypeScript自动推断返回类型 export const incrementByOne = () => ({ type: ActionTypes.INCREMENT_BY_ONE, } as const); // 用as const强制推断为字面量类型 export const incrementByMultiple = (multiple: number) => ({ type: ActionTypes.INCREMENT_BY_MULTIPLE, payload: multiple, } as const); // 自动生成所有Action的联合类型 type ActionCreators = typeof incrementByOne | typeof incrementByMultiple; export type KnownAction = ReturnType<ActionCreators>; function reducer(state: number = 0, action: KnownAction) { switch(action.type) { case ActionTypes.INCREMENT_BY_ONE: // TypeScript会正确识别这里没有payload,尝试访问会报错(符合预期) // const a = action.payload; ❌ return state + 1; case ActionTypes.INCREMENT_BY_MULTIPLE: // payload自动推断为number类型 return state * action.payload; ✅ default: return state; } }
为什么这能行?as const告诉TypeScript把对象的属性推断为字面量类型(比如ActionTypes.INCREMENT_BY_ONE而不是宽泛的ActionTypes),这样TypeScript就能在switch case中准确收窄Action的类型,自动识别每个case对应的payload是否存在及其类型。
方法2:保留类结构,用as const简化
如果你想继续用类来组织Action creators,只需要在每个方法的返回值后加上as const,就能去掉显式类型转换:
export enum ActionTypes { INCREMENT_BY_ONE = 'INCREMENT_BY_ONE', INCREMENT_BY_MULTIPLE = 'INCREMENT_BY_MULTIPLE', } class Actions { incrementByOne() { return { type: ActionTypes.INCREMENT_BY_ONE } as const; } incrementByMultiple(multiple: number) { return { type: ActionTypes.INCREMENT_BY_MULTIPLE, payload: multiple } as const; } } type ValueOf<T> = T[keyof T]; export type KnownAction = ReturnType<ValueOf<Actions>>; function reducer(state: number = 0, action: KnownAction) { switch(action.type) { case ActionTypes.INCREMENT_BY_ONE: // 正确识别无payload return state + 1; case ActionTypes.INCREMENT_BY_MULTIPLE: // payload类型为number return state * action.payload; default: return state; } }
这种方式和你原来的类结构兼容,同时避免了重复的类型断言代码。
方法3:显式指定方法返回类型(更严谨但稍繁琐)
如果你不想用as const,也可以手动给每个类方法指定返回类型,同样能达到类型安全的效果:
export enum ActionTypes { INCREMENT_BY_ONE = 'INCREMENT_BY_ONE', INCREMENT_BY_MULTIPLE = 'INCREMENT_BY_MULTIPLE', } class Actions { incrementByOne(): { type: ActionTypes.INCREMENT_BY_ONE } { return { type: ActionTypes.INCREMENT_BY_ONE }; } incrementByMultiple(multiple: number): { type: ActionTypes.INCREMENT_BY_MULTIPLE; payload: number } { return { type: ActionTypes.INCREMENT_BY_MULTIPLE, payload: multiple }; } } type ValueOf<T> = T[keyof T]; export type KnownAction = ReturnType<ValueOf<Actions>>; function reducer(state: number = 0, action: KnownAction) { switch(action.type) { case ActionTypes.INCREMENT_BY_ONE: return state + 1; case ActionTypes.INCREMENT_BY_MULTIPLE: return state * action.payload; default: return state; } }
这种方式需要手动定义每个方法的返回类型,虽然更繁琐,但类型定义更清晰,适合对类型严谨性要求更高的场景。
总结
- 优先推荐方法1:函数式Action creators +
as const,最简洁且类型安全。 - 如果必须保留类结构,方法2是最优选择,用
as const简化代码。 - 方法3适合需要显式类型定义的场景,但稍显繁琐。
所有这些方法都能让你去掉那些重复的<ActionTypes.X>ActionTypes.X类型断言,同时保持Reducer中Action.payload的类型正确识别。
内容的提问来源于stack exchange,提问作者Dennis
相关产品推荐
相关产品推荐

