如何将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
相关产品推荐
相关产品推荐

