Redux saga 更新嵌套的对象状态数组

问题描述

我有一个 redux 切片实体,我用它来存储数组的状态。数组本身包含对象数组的嵌套属性。现在我需要在 redux saga 中更新该状态,并且我试图创建另一个生成函数,但无法弄清楚如何更新状态。我只需要更新状态,稍后将进行 API 调用。如何更新状态?

这里是task和deliveryParcels的接口

export interface IRiderTask {
  _id?: any;
  riderId: string;
  totalAmount: number;
  riderName: string;
  cityId: string;
  cityName: string;
  status: string;
  adminName: string;
  adminId: string;
  createdAt: Date;
  deliveryParcels: IDeliveryParcels[];
}

export interface IDeliveryParcels {
  parcelId: string;
  processingStatus?: string;
  amount: number;
  orderType: 'COD' | 'NONCOD';
  postedStatus?: {
    status: string;
    statusKey: string;
    signature?: string;
    checkBoxData?: any[];
    reason: string;
    adminId?: string;
    adminName?: string;
  };
}

我每次都会用不同的值更新postedStatus对象,所以需要在saga的生成函数中处理它。

这是我的传奇

import {
  createAsyncThunk,createEntityAdapter,createSelector,createSlice,EntityState,PayloadAction,} from '@reduxjs/toolkit';
import { IRiderTask } from '@swyft/interfaces';
import { put,takeEvery } from 'redux-saga/effects';
import { select } from 'typed-redux-saga/dist';
import { DeliveryManagementState } from '../state';

export const finalStatus_FEATURE_KEY = 'finalStatus';
type Processing = 'initial' | 'processing' | 'processed' | 'processing error';

/*
 * Update these interfaces according to your requirements.
 */
export interface finalStatusEntity extends IRiderTask {
  _id?: any;
}

export interface finalStatusstate extends EntityState<finalStatusEntity> {
  loadingStatus: Processing;
  updatingRequest: Processing;
  error: string | null;
}

export const finalStatusAdapter = createEntityAdapter<finalStatusEntity>({
  selectId: (e) => e._id,});

export const initialfinalStatusstate: finalStatusstate = finalStatusAdapter.getinitialState(
  {
    loadingStatus: 'initial',updatingRequest: 'initial',error: null,}
);

export const finalStatusSlice = createSlice({
  name: finalStatus_FEATURE_KEY,initialState: initialfinalStatusstate,reducers: {
    add: finalStatusAdapter.addOne,remove: finalStatusAdapter.removeOne,setLoading: (state,action: PayloadAction<Processing>) => {
      state.loadingStatus = action.payload;
    },setTasksstate: (state,action: PayloadAction<{ data: any }>) => {},setUpdatedState: (state,action: PayloadAction<Processing>) => {
      state.updatingRequest = action.payload;
    },},});

/*
 * Export reducer for store configuration.
 */
export const finalStatusReducer = finalStatusSlice.reducer;

export const finalStatusActions = finalStatusSlice.actions;

const { selectAll,selectEntities } = finalStatusAdapter.getSelectors();

export const getfinalStatusstate = (
  rootState: DeliveryManagementState
): finalStatusstate => rootState.deliveryPilet.finalStatus;

export const selectAllfinalStatus = createSelector(
  getfinalStatusstate,selectAll
);

export const selectfinalStatusEntities = createSelector(
  getfinalStatusstate,selectEntities
);

export function* finalStatusRootSaga() {
  yield takeEvery('finalStatus/setTasksstate',setTasksstate);
  yield takeEvery('finalStatus/updateTaskState',updateTaskState);
}

function* setTasksstate({ payload: { data } }: { payload: { data: any } }) {
  console.log('In slice',data);

  yield put(finalStatusActions.add(data));
  console.log(data);
}

function* updateTaskState(action: PayloadAction<{ id: string; data: any }>) {
  yield put(finalStatusActions.setUpdatedState('processing'));
  yield put(
    finalStatusActions.setUpdatedState({
      id: action.payload.id,...action.payload.data,})
  );
  yield put(finalStatusActions.setUpdatedState('processed'));
}

解决方法

这里有两个不同的问题。一种是创建操作和减速器案例以正确更新数据。另一个是通过 saga 调度其中的一些动作。坦率地说,我真的不明白你的传奇应该做什么,我不确定你是否也这样做。但我绝对可以做出一些改进。

“处理”状态适用于整个切片,还是适用于每个任务?

与其通过更新数组的元素来更新parcel,我认为最好将它们视为一个单独的实体。

传奇

import { EntityId,PayloadAction,Update } from "@reduxjs/toolkit";
import { finalStatusActions,FinalStatusEntity } from "./slice";
import { put,all,takeEvery,call } from "redux-saga/effects";

// dummy for API call
const postUpdates = async (
  id: EntityId,changes: Partial<FinalStatusEntity>
): Promise<Partial<FinalStatusEntity>> => {
  return changes;
};

function* updateTaskState(action: PayloadAction<Update<FinalStatusEntity>>) {
  const { id,changes } = action.payload;
  try {
    const result = yield call(postUpdates,id,changes);
    yield put(
      finalStatusActions.updateTaskStateSuccess({
        id,changes: { ...result,status: "processed" }
      })
    );
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    yield put(
      finalStatusActions.updateTaskStateFailure({ id,error: message })
    );
  }
}

export function* finalStatusRootSaga() {
  yield all([
    takeEvery(finalStatusActions.updateTaskState.type,updateTaskState)
  ]);
}

export default finalStatusRootSaga;

商店

import {
  AnyAction,combineReducers,configureStore,getDefaultMiddleware
} from "@reduxjs/toolkit";
import {
  createSelectorHook,useDispatch as useBasicDispatch
} from "react-redux";
import finalStatus,{ parcelSlice } from "./slice";
import createSagaMiddleware from "redux-saga";
import mySaga from "./saga";

const sagaMiddleware = createSagaMiddleware();

const deliveryPilet = combineReducers({
  finalStatus,parcels: parcelSlice.reducer
});

const store = configureStore({
  reducer: {
    deliveryPilet
  },middleware: [...getDefaultMiddleware(),sagaMiddleware]
});

sagaMiddleware.run(mySaga);

export type RootState = ReturnType<typeof store.getState>;

export type DeliveryManagementState = RootState;

export type AppDispatch = typeof store.dispatch;

export type Action = AnyAction;

export const useDispatch = (): AppDispatch => useBasicDispatch();

export const useSelector = createSelectorHook<RootState>();

export default store;

任务

// helper for adding a task
const taskToEntity = (task: IRiderTask): FinalStatusEntity => ({
  ...task,parcelIds: task.deliveryParcels.map((o) => o.parcelId)
});

export const finalStatusAdapter = createEntityAdapter<FinalStatusEntity>({
  selectId: (entity) => entity._id
});

export const initialfinalStatusState = finalStatusAdapter.getInitialState();

export const finalStatusSlice = createSlice({
  name: finalStatus_FEATURE_KEY,initialState: initialfinalStatusState,reducers: {
    // accept in the form of an IRiderTask instead of FinalStatusEntity
    addTask: (state,action: PayloadAction<IRiderTask>) => {
      finalStatusAdapter.addOne(state,taskToEntity(action.payload));
    },removeTask: finalStatusAdapter.removeOne,updateTaskState: (
      state,action: PayloadAction<Update<FinalStatusEntity>>
    ) => {
      // here we just start the update,the saga will dispatch the rest
      const { id } = action.payload;
      finalStatusAdapter.updateOne(state,{
        id,changes: { status: "processing" }
      });
    },updateTaskStateSuccess: finalStatusAdapter.updateOne,updateTaskStateFailure: (
      state,action: PayloadAction<{ id: EntityId; error: string }>
    ) => {
      const { id,error } = action.payload;
      finalStatusAdapter.updateOne(state,changes: { status: "processing error",error }
      });
    }
  }
});

export const finalStatusActions = finalStatusSlice.actions;

export default finalStatusReducer;

包裹

export const parcelAdapter = createEntityAdapter<IDeliveryParcels>({
  selectId: (parcel) => parcel.parcelId
});

export const parcelSlice = createSlice({
  name: "parcels",initialState: parcelAdapter.getInitialState(),reducers: {
    updateParcel: parcelAdapter.updateOne,// handle nested update
    updatePostedStatus: (
      state,action: PayloadAction<Update<IPostedStatus>>
    ) => {
      const { id,changes } = action.payload;
      const current = state.entities[id];
      if (current) {
        current.postedStatus = {
          ...current.postedStatus,...changes
        } as IPostedStatus; // I don't know why this is necessary,but I'm getting TS errors
      }
    }
  },extraReducers: {
    // add the parcels when adding a task
    [finalStatusActions.addTask.type]: (
      state,action: PayloadAction<IRiderTask>
    ) => {
      parcelAdapter.upsertMany(state,action.payload.deliveryParcels);
    }
  }
});

类型

import { EntityId } from "@reduxjs/toolkit";

export interface IRiderTask {
  _id: EntityId;
  riderId: string;
  totalAmount: number;
  riderName: string;
  cityId: string;
  cityName: string;
  status: string;
  adminName: string;
  adminId: string;
  createdAt: Date;
  deliveryParcels: IDeliveryParcels[];
}

export interface IPostedStatus {
  status: string;
  statusKey: string;
  signature?: string;
  checkboxData?: any[];
  reason: string;
  adminId?: string;
  adminName?: string;
}

export interface IDeliveryParcels {
  parcelId: string;
  processingStatus?: string;
  amount: number;
  orderType: "COD" | "NONCOD";
  postedStatus?: IPostedStatus;
}

export type Processing =
  | "initial"
  | "processing"
  | "processed"
  | "processing error";

// replace parcel objects with ids,include error
export type FinalStatusEntity = Omit<IRiderTask,"deliveryParcels"> & {
  parcelIds: string[];
  error?: string;
};

Code Sandbox Link