TypeScript 2.8+:如何捕获并映射Action Creator到Action Handler?
嘿,我来帮你搞定这个TypeScript泛型的问题,让你的action handler能自动精准推导action的类型~
首先,先回顾一下你已经定义好的action creator代码:
/** simply captures the string literal type */ function actionType<T extends string, U>(type: T, u: U) { return Object.assign({}, { type }, u); } // ------------------------------------ // Action Creators // ------------------------------------ const ACTION_CREATORS = { toggleFilter: (params: { filterValueKey: string; filterKey: string; filterValue: string }) => { const { filterKey, filterValue, filterValueKey } = params; return actionType('FILTERS_TOGGLE', { payload: { filterValueKey, filterKey, filterValue } }); }, deleteFilter: (params: { filterValueKey: string }) => { const { filterValueKey } = params; return actionType('FILTERS_DELETE', { payload: { filterValueKey } }); }, searchFilterValues: (params: { activeFilterKey: string; query: string }) => { const { query, activeFilterKey } = params; return actionType('FILTERS_QUERY_VALUES', { payload: { query, activeFilterKey } }); } };
接下来我们来实现你需要的ActionHandlers泛型,核心思路是从action creators里提取每个action的具体类型,然后映射到对应的handler函数上:
第一步:定义辅助类型
先写几个基础的辅助类型,用来提取和转换action相关的类型:
// 从单个action creator函数中提取它返回的action类型 type ActionFromCreator<T> = T extends (...args: any[]) => infer Action ? Action : never; // 将action creators对象映射为 { [creatorKey: string]: Action } 的类型 type ActionsMap<T> = { [K in keyof T]: ActionFromCreator<T[K]>; }; // 提取所有action的type字段,得到联合类型(比如 "FILTERS_TOGGLE" | "FILTERS_DELETE" | ...) type ActionTypes<T> = ActionsMap<T>[keyof T]['type']; // 根据指定的action type,找到对应的具体action类型 type ActionForType<T, Type extends string> = Extract<ActionsMap<T>[keyof T], { type: Type }>;
第二步:实现核心的ActionHandlers泛型
基于上面的辅助类型,我们可以写出最终的ActionHandlers:
type ActionHandlers<T extends Record<string, (...args: any[]) => { type: string }>> = { // 遍历所有action type,为每个type生成对应的handler函数 [Type in ActionTypes<T>]: (state: any, action: ActionForType<T, Type>) => any; };
第三步:测试你的ACTION_HANDLERS
现在用这个泛型来标注你的handler对象,每个handler里的action参数都会自动推导出准确的类型,包括payload的属性:
const ACTION_HANDLERS: ActionHandlers<typeof ACTION_CREATORS> = { FILTERS_TOGGLE: (state, action) => { // 这里三个payload属性都会被正确推断为string类型 action.payload.filterValueKey; action.payload.filterKey; action.payload.filterValue; const newState = { ...state }; return newState; }, FILTERS_DELETE: (state, action) => { // action.payload.filterValueKey 类型正确 action.payload.filterValueKey; return state; }, FILTERS_QUERY_VALUES: (state, action) => { // action.payload.query 和 activeFilterKey 类型正确 action.payload.query; action.payload.activeFilterKey; const newState = { ...state }; return newState; } };
额外优化:给State也加上类型
如果你想让state参数也有强类型约束,可以给ActionHandlers再加一个泛型参数:
// 扩展后的泛型,支持指定State类型 type ActionHandlers<State, T extends Record<string, (...args: any[]) => { type: string }>> = { [Type in ActionTypes<T>]: (state: State, action: ActionForType<T, Type>) => State; }; // 假设你的状态类型是这样的 type FilterState = { activeFilters: Record<string, string>; // 其他状态字段... }; // 使用时指定State类型 const ACTION_HANDLERS: ActionHandlers<FilterState, typeof ACTION_CREATORS> = { FILTERS_TOGGLE: (state, action) => { // state现在有FilterState的类型约束 const newState = { ...state }; // 处理逻辑... return newState; }, // 其他handler... };
这样不仅action类型精准,state也会有严格的类型检查,而且handler必须返回和输入相同的State类型,避免类型不一致的问题。
内容的提问来源于stack exchange,提问作者Rico Kahler
相关产品推荐
相关产品推荐

