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

如何将NGRX状态树构造成期望的嵌套数组结构?

调整NGRX状态结构:从嵌套对象到数组友好型结构

首先得澄清一个小误区:你描述的期望结构里selectedShifts: [ [employeeId]: [ [shiftId]: shift ] ]其实在JavaScript/TypeScript里是语法错误的——数组不能使用自定义键(比如employeeId),数组是按索引排序的集合。我猜你真正想要的是让每个员工对应的选中班次是数组(而非对象),这样增删查操作更顺手,对吧?

下面分步骤帮你完成状态结构的调整:

1. 定义正确的状态类型(TypeScript)

先把状态结构用接口明确下来,推荐两种常见的合理方案:

方案一:按员工ID索引,值为班次数组(推荐)

这种方案兼顾了按员工ID查找的效率和班次数组的操作便捷性:

// 先定义Shift类型
interface Shift {
  id: string; // 对应你的shiftId
  // 其他班次属性,比如date、hours等
}

// 子状态接口
interface ShiftsState {
  selectedShifts: Record<string, Shift[]>; // 键是employeeId,值是该员工的选中班次数组
}

// 根状态接口
interface AppState {
  shifts: ShiftsState;
}

方案二:整体为数组(适合需要按顺序遍历员工的场景)

如果需要把所有员工的选中班次作为有序数组管理,可以用这种结构:

interface SelectedEmployeeShifts {
  employeeId: string;
  shifts: Shift[];
}

interface ShiftsState {
  selectedShifts: SelectedEmployeeShifts[];
}

下面以方案一为例展开操作,因为它更符合大多数业务场景的需求。

2. 迁移现有状态数据

如果你的应用已经有旧状态数据(就是现在的{ [employeeId]: { [shiftId]: shift } }结构),需要把它转换为新结构。可以写一个转换函数,在reducer的初始状态中使用:

// 转换旧状态到新结构
const convertOldSelectedShifts = (oldState: Record<string, Record<string, Shift>>): Record<string, Shift[]> => {
  return Object.entries(oldState).reduce((acc, [employeeId, shiftsObj]) => {
    // 把每个员工的班次对象转成数组
    acc[employeeId] = Object.values(shiftsObj);
    return acc;
  }, {} as Record<string, Shift[]>);
};

// 初始化reducer状态时使用转换函数
const initialState: ShiftsState = {
  selectedShifts: convertOldSelectedShifts(
    // 这里传入旧的selectedShifts数据,比如从localStorage加载的旧状态
    // 如果是全新状态,直接传{}即可
  )
};

3. 更新Reducer的操作逻辑

现在针对新结构编写增删查的action处理,操作数组比对象直观太多:

添加选中班次

// 定义Action
import { createAction, createReducer, on } from '@ngrx/store';

export const addSelectedShift = createAction(
  '[Shifts] Add Selected Shift',
  props<{ employeeId: string; shift: Shift }>()
);

// Reducer处理
const shiftsReducer = createReducer(
  initialState,
  on(addSelectedShift, (state, { employeeId, shift }) => {
    // 获取该员工已有的班次数组,没有则初始化空数组
    const existingShifts = state.selectedShifts[employeeId] || [];
    // 避免重复添加(如果业务需要允许重复可以去掉这步)
    if (!existingShifts.some(s => s.id === shift.id)) {
      return {
        ...state,
        selectedShifts: {
          ...state.selectedShifts,
          [employeeId]: [...existingShifts, shift]
        }
      };
    }
    return state; // 班次已存在则不修改状态
  })
);

删除选中班次

export const removeSelectedShift = createAction(
  '[Shifts] Remove Selected Shift',
  props<{ employeeId: string; shiftId: string }>()
);

on(removeSelectedShift, (state, { employeeId, shiftId }) => {
  const existingShifts = state.selectedShifts[employeeId] || [];
  // 过滤掉要删除的班次
  const updatedShifts = existingShifts.filter(s => s.id !== shiftId);
  return {
    ...state,
    selectedShifts: {
      ...state.selectedShifts,
      [employeeId]: updatedShifts
    }
  };
});

清空某个员工的所有选中班次

export const clearEmployeeSelectedShifts = createAction(
  '[Shifts] Clear Employee Selected Shifts',
  props<{ employeeId: string }>()
);

on(clearEmployeeSelectedShifts, (state, { employeeId }) => {
  // 解构移除该员工的条目
  const { [employeeId]: _, ...restSelectedShifts } = state.selectedShifts;
  return {
    ...state,
    selectedShifts: restSelectedShifts
  };
});

4. 更新Selector适配新结构

编写selector让组件更方便地获取状态数据:

import { createSelector } from '@ngrx/store';

// 先定义获取shifts子状态的selector
export const selectShiftsState = (state: AppState) => state.shifts;

// 获取所有选中班次的整体结构
export const selectSelectedShifts = createSelector(
  selectShiftsState,
  (state) => state.selectedShifts
);

// 获取指定员工的选中班次
export const selectSelectedShiftsByEmployee = createSelector(
  selectSelectedShifts,
  (state, props: { employeeId: string }) => state[props.employeeId] || []
);

// 获取所有选中班次的扁平化数组(比如需要统计所有选中班次时用)
export const selectAllSelectedShiftsFlat = createSelector(
  selectSelectedShifts,
  (state) => Object.values(state).flat()
);

这样调整后,你就可以用数组的filter、map、some等便捷方法来处理班次的增删查,比之前操作嵌套对象顺手多了。

内容的提问来源于stack exchange,提问作者rawk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:26:41