如何通过NGXS内置API编程式选择嵌套属性更新状态
NGXS 动态属性嵌套状态更新方案
使用NGXS内置的setState、patch、updateItemAPI更新深层嵌套状态时,直接传入计算属性名[action.taskType]做动态属性更新会遇到类型校验问题,原有手动重建整个状态对象的写法冗余、可维护性差。
下面给出基于官方操作符的正确实现。
相关定义参考
- 根状态模型
interface TaskStateModel { tasks: Tasks; }
- Tasks类
export class Tasks { // 属性名已做混淆处理 property1: UniqueTask[] proprety2: UniqueTask[] // 其余属性省略... constructor(init?: Partial<Tasks>) { Object.assign(this, init); } }
- UniqueTask类
export class UniqueTask { // 仅展示相关属性 taskIds: string[]; asssignedUserName: string; constructor(init?: Partial<UniqueTask>) { Object.assign(this, init); } }
- AssignTask动作
export class AssignTask { static readonly type = '[Task] Assign Task'; constructor(public taskIds: string[], public taskType: string) {} }
原有冗余实现
旧方案需要手动遍历所有状态属性重建对象,代码冗余度高:
@Action(AssignTask) assignTasks(ctx: StateContext<TaskStateModel>, action: AssignTask) { return this.taskService.assignTasks(action.taskIds).pipe( tap(response => { if (response.ok) { action.taskIds.forEach(taskId => { const state = ctx.getState(); const newState: TaskStateModel = {tasks: new Tasks()}; const taskIndexToBeAssigned = state.tasks[action.taskType].findIndex(task => task.taskIds.includes(taskId)); Object.keys(state.tasks).forEach(property => { if (property !== action.taskType) { newState.tasks[property] = state.tasks[property]; } else { newState.tasks[property] = state.tasks[property].map((task, index) => { const assignedUserName = (taskIndexToBeAssigned === index) ? this.currentUserService.currentUser.name : task.assignedUserName; return new UniqueTask({ ...task, assignedUserName }); }); } }); ctx.setState(newState); }); } }) ); }
正确实现方案
patch方法原生支持ES6计算属性名,之前的报错本质是TypeScript类型校验问题,同时原尝试代码存在数组方法调用错误、重复触发状态更新的问题,修正后代码如下:
import { patch, updateItem } from '@ngxs/store/operators'; @Action(AssignTask) assignTasks(ctx: StateContext<TaskStateModel>, action: AssignTask) { return this.taskService.assignTasks(action.taskIds).pipe( tap(response => { if (!response.ok) return; const currentUserName = this.currentUserService.currentUser.name; // 构造动态属性更新配置 const taskTypePatch = { [action.taskType]: updateItem<UniqueTask>( // 匹配所有包含目标taskId的任务项 task => action.taskIds.some(taskId => task.taskIds.includes(taskId)), // 更新指派人字段 task => ({ ...task, assignedUserName: currentUserName }) ) }; ctx.setState( patch({ tasks: patch(taskTypePatch as any) }) ); }) ); }
优化点说明
- 去掉了循环单个taskId重复调用
setState的逻辑,一次性匹配所有需要更新的任务,仅触发一次状态变更,性能更好 - 修正了原尝试代码的语法错误:
task.taskIds(taskId)是错误的函数调用写法,taskIds为数组类型,需要用includes方法判断元素是否存在 - 类型报错可通过两种方式解决:
- 临时使用
as any类型断言绕过校验 - 从根源约束
taskType类型,修改AssignTask类定义,将taskType的类型从string改为keyof Tasks,TS即可自动识别计算属性的合法性,无需类型断言:export class AssignTask { static readonly type = '[Task] Assign Task'; constructor( public taskIds: string[], public taskType: keyof Tasks ) {} }
- 临时使用
内容的提问来源于stack exchange,提问作者Ecke
相关产品推荐
相关产品推荐

