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

Redux saga update a nested array of object state

我有一个 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 调度其中的一些动作。坦率地说,我真的不明白你的传奇应该做什么,我不确定你是否也这样做。但我绝对可以做出一些改进。

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

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

传奇

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, {
        id,
        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