基于Redux Toolkit的CRUD基类Reducer状态覆盖问题求助
问题分析与解决方案
问题根源
你遇到的状态被覆盖问题,核心原因是全局定义的initialState对象被所有CRUDReducer实例共享。因为JavaScript对象是引用类型,所有切片在创建时都指向同一个内存地址的初始状态,当任意一个切片的状态更新时,会直接修改这个共享对象,导致所有切片的状态都同步变化。
另外还有两个次要问题:
createAsyncThunk的泛型参数定义错误,不符合Redux Toolkit的类型规范- 类属性
getListAsync使用any类型,失去TypeScript的类型校验能力
修复方案
1. 为每个实例创建独立的初始状态
将initialState的定义移到类的构造函数内部,确保每个切片实例拥有独立的初始状态对象。
2. 修正类型定义
规范createAsyncThunk的泛型参数,为getListAsync指定正确的类型,避免使用any。
3. 使用Redux Toolkit推荐的Builder API
用builder模式替代对象形式的extraReducers,避免属性名计算导致的潜在绑定问题。
修改后的CRUDReducer代码
// 定义基础类型(根据实际项目调整) interface BaseState { isLoading: boolean; objectsArray: any[]; errorDescription: string; paginator?: any; } interface KeyValuePair { key: string; value: any; } interface RootState { [key: string]: BaseState; } // 模拟Http请求类(根据实际项目替换) class Http { static get(url: string) { return Promise.resolve({ result: [], paginator: {} }); } } import { createAsyncThunk, createSlice, AsyncThunk } from '@reduxjs/toolkit'; export class CRUDReducer { readonly nameSlice: string; readonly urlSlice: string; getListAsync: AsyncThunk<any, KeyValuePair[], { state: RootState }>; reducerSlice: ReturnType<typeof createSlice>; constructor(nameSlice: string, url: string) { this.nameSlice = nameSlice; this.urlSlice = url; // 初始化实例独立的getListAsync this.getListAsync = createAsyncThunk( `${this.nameSlice}/getList`, async (urlParams: KeyValuePair[]) => { let requestUrl = `api/${this.urlSlice}`; // 可在此处拼接urlParams参数 const getResult = await Http.get(requestUrl); return getResult; } ); // 为当前实例创建独立初始状态 const initialState: BaseState = { isLoading: true, objectsArray: [], errorDescription: "", }; // 创建切片 this.reducerSlice = createSlice({ name: this.nameSlice, initialState, reducers: {}, extraReducers: (builder) => { builder .addCase(this.getListAsync.pending, (state) => { state.isLoading = true; }) .addCase(this.getListAsync.fulfilled, (state, action) => { if (action.payload.error !== undefined && action.payload.error != null) { state.errorDescription = action.payload.errorDescription; } else { state.objectsArray = action.payload.result; state.paginator = action.payload.paginator; } state.isLoading = false; }) .addCase(this.getListAsync.rejected, (state, action) => { console.error(`${this.nameSlice}/getListError`, action); state.isLoading = false; state.errorDescription = 'Error! Try again'; }); }, }); } }
实例化切片的代码(保持原有逻辑即可)
import { RootState } from '../store'; import { CRUDReducer } from './CRUDReducer'; const baseReducer = new CRUDReducer( 'accountParkingPlaces', 'parking/places' ); export const { getListAsync } = baseReducer; // 若需createAsync等方法,需在基类补充后再解构 const accountParkingPlacesSlice = baseReducer.reducerSlice; export const selectList = (state: RootState) => state.accountParkingPlaces.objectsArray; export const selectIsLoading = (state: RootState ) => state.accountParkingPlaces.isLoading; export default accountParkingPlacesSlice.reducer;
额外说明
- 若要补充
createAsync、updateAsync、deleteAsync等方法,需按照getListAsync的模式在类中实现,确保每个方法都是实例独立的 - 确保
BaseState、RootState等类型定义与项目实际结构匹配
内容的提问来源于stack exchange,提问作者Никита Деревянных
相关产品推荐
相关产品推荐

