You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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,提问作者Никита Деревянных

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 03:06:22