NGXS集成报错:Cannot add property 0, object is not extensible
问题背景
该项目为待办清单类应用,支持创建按Sprint(迭代)分组的任务,同时可给任务指派负责人、设置负责人完成任务的预估工时。
接入NGXS状态管理时出现异常:在createTask方法中添加this.store.dispatch(new AddTask(task));代码行前应用运行正常,添加该行后,为任务指派负责人的changePersonAssigneInTask方法无法正常执行,浏览器控制台抛出如下错误:
ERROR TypeError: Cannot add property 0, object is not extensible at Array.push (<anonymous>) at DataBaseService.addPersonToTask (data-base.service.ts:78:20) at DataBaseService.changePersonAssigneInTask (data-base.service.ts:63:16) at DataService.changePersonAssigneInTask (data.service.ts:37:26) at TaskComponent.changePersonAssigneInTask (task.component.ts:68:22) at TaskComponent_div_20_Template_button_click_9_listener (task.component.html:49:28) at executeListenerWithErrorHandling (core.mjs:14979:1) at wrapListenerIn_markDirtyAndPreventDefault (core.mjs:15017:1) at HTMLButtonElement.<anonymous> (platform-browser.mjs:466:38) at _ZoneDelegate.invokeTask (zone.js:406:1)
相关代码
接口定义
export interface Sprint { personCapacities: PersonCapacity[]; tasks: Task[]; dates: Dates[]; name: string; } export interface Task { number: string; name: string; assignees: Assignee[]; } export interface Assignee { person: Person; hours: number; }
任务创建逻辑
createTask() { const task: Task = { number: this.taskNumber, name: this.taskName, assignees: [] }; this.dataService.addTask(task, this.chosenSprint); this.store.dispatch(new AddTask(task)); }
dataService.addTask 实现
addTask(task: Task, chosenSprint: Sprint) { this.sprints.find(value => { if(value.name==chosenSprint.name) value.tasks.push(task) } ) }
任务指派负责人逻辑入口
changePersonAssigneInTask(person: Person, hours: number, task: Task) { this.dataService.changePersonAssigneInTask(person, hours, task); }
dataService.changePersonAssigneInTask 实现
changePersonAssigneInTask(person: Person, hours: number, task: Task) { let assignee = task.assignees.find(p => p.person.shortName == person.shortName); if ( assignee == null) { this.addPersonToTask(person, hours, task); } else { this.updatePersonHoursInTask(person, hours, task, assignee); } }
dataService.addPersonToTask 实现(错误触发位置)
addPersonToTask(person: Person, hours: number, task: Task){ task.assignees.push({ //<--该行抛出错误 person, hours }); }
NGXS 相关代码
actions.ts
import {Task} from "../../models/task"; export class AddTask{ static readonly type = '[TASK] Add Task'; constructor(public task: Task) { } }
state.ts
import {Action, Selector, State, StateContext} from '@ngxs/store'; import {AddTask} from '../actions/actions'; import {Task} from "../../models/task"; import {Injectable} from "@angular/core"; import {Role} from "../../models/person"; export interface TaskInterface { tasks: Task[]; } @State<TaskInterface>({ name: 't', defaults: { tasks: [] } }) @Injectable() export class AddTaskAction{ @Action(AddTask) AddTask(ctx: StateContext<TaskInterface>, action: AddTask){ const {task} = action; const state = ctx.getState(); if(!task) return; ctx.setState({ ...state, tasks: [...state.tasks, task] }); console.log(ctx.getState()); } }
问题原因
NGXS默认会在开发模式下对存入状态的所有对象做深度冻结,禁止外部代码直接修改状态,保证状态变更只能通过action触发。你在dispatch action时直接把原task对象的引用传入了状态,NGXS冻结这个对象后,它内部的assignees数组也会被冻结为不可扩展状态,后续直接调用task.assignees.push()修改原对象,就会抛出object is not extensible的错误。
修复方案
任选一种方案即可解决问题:
- 传入NGXS状态时对对象做拷贝,避免原对象被冻结。dispatch时不要直接传原task的引用,传入拷贝后的新对象,这样NGXS冻结的是拷贝对象,不会影响业务流程中使用的原task对象。修改
createTask方法中的dispatch代码:// 做嵌套浅拷贝,保证assignees数组是新的引用 this.store.dispatch(new AddTask({ ...task, assignees: [...task.assignees] })); - 遵循NGXS单向数据流规范,所有涉及任务修改的操作(包括添加负责人、更新工时)都通过派发action完成,在state的action handler里统一更新状态,不要在外部service直接修改被状态持有的对象引用。
内容的提问来源于stack exchange,提问作者Maikki
相关产品推荐
相关产品推荐

