如何为Redux Toolkit编写带TS类型的通用创建项Reducer构建函数?
解决方案
一、修正类型定义:绑定Key与数组元素类型
问题根源是TypeScript无法推断KeysMatching<State, T[]>与State的索引关系,可通过将key作为核心类型参数,让TypeScript自动推导对应的数组元素类型:
写法1:内联条件类型约束
import { PayloadAction } from '@reduxjs/toolkit'; // 替换为你的实际State类型 type State = { something_set: { id: number; name: string }[]; another_set: { id: number; value: number }[]; non_list_prop: string; }; // 假设getId的类型声明 declare function getId<T extends { id: number }>(list: T[]): number; function createWithId<K extends keyof State>( key: K extends State[K] extends Array<infer T> ? K : never ) { return ( state: State, action: PayloadAction<Omit<State[K] extends Array<infer T> ? T : never, "id">> ) => { const lst = state[key] as Array<{ id: number } & Omit<State[K] extends Array<infer T> ? T : never, "id">>; const id = getId(lst); lst.push({ ...action.payload, id } as State[K] extends Array<infer T> ? T : never); }; } // 使用示例:自动推导类型,非数组键会报错 const createSomething = createWithId('something_set'); const createAnother = createWithId('another_set'); // const createInvalid = createWithId('non_list_prop'); // 类型错误
写法2:预定义辅助类型(更易读)
// 提取State中所有对应数组类型的键 type StateArrayKeys = { [K in keyof State]: State[K] extends Array<unknown> ? K : never; }[keyof State]; // 根据键获取对应的数组元素类型 type ArrayElement<K extends StateArrayKeys> = State[K] extends Array<infer T> ? T : never; function createWithId<K extends StateArrayKeys>(key: K) { return ( state: State, action: PayloadAction<Omit<ArrayElement<K>, "id">> ) => { const lst = state[key]; const id = getId(lst); lst.push({ ...action.payload, id } as ArrayElement<K>); }; }
这种写法通过预定义辅助类型,让代码更清晰,同时TypeScript会自动验证key的合法性,并正确推导action.payload的类型,解决索引错误问题。
二、Redux Toolkit更优方案:使用createEntityAdapter
如果你的切片用于管理实体集合,RTK内置的createEntityAdapter是更高效的选择,它封装了所有常见的CRUD操作(包括带ID的实体添加),无需重复编写Reducer:
import { createSlice, createEntityAdapter } from '@reduxjs/toolkit'; // 定义实体类型 type Something = { id: number; name: string }; // 创建实体适配器,自动生成CRUD逻辑 const somethingAdapter = createEntityAdapter<Something>({ // 可自定义ID提取逻辑,默认取entity.id // selectId: (entity) => entity.id, }); // 初始化规范化状态(包含ids和entities字段) const initialState = somethingAdapter.getInitialState(); const somethingSlice = createSlice({ name: 'something', initialState, reducers: { // 直接使用适配器提供的addOne方法,自动处理ID与添加逻辑 createSomething: somethingAdapter.addOne, // 其他预定义方法:addMany、updateOne、deleteOne、upsertOne等 }, }); export const { createSomething } = somethingSlice.actions; // 自动生成的selector,用于从状态中获取数据 export const somethingSelectors = somethingAdapter.getSelectors((state) => state.something);
createEntityAdapter不仅减少重复代码,还采用规范化状态结构提升性能,同时提供预定义的selector,比自定义Reducer更健壮、易维护。
内容的提问来源于stack exchange,提问作者Matija Sirk
相关产品推荐
相关产品推荐

