Angular 5响应式表单中修改对象副本致原对象变更问题
我在Angular里用响应式表单构建了一个任务表单,提交前想复制表单的value对象,修改副本再发给服务器,但发现修改副本时原表单的value也跟着变了,这是怎么回事?
表单构建代码:
newTask: FormGroup; this.newTask = this.formBuilder.group({ partner: [null, [Validators.required]], title: [null, [Validators.required, Validators.minLength(5), Validators.maxLength(50)]], description: [null, [Validators.minLength(10), Validators.maxLength(120)]], responsible: [null, [Validators.required]], type: [null, [Validators.required]], priority: [null, [Validators.required]], status: [null, [Validators.required]], history: this.formBuilder.array([]) })
添加/移除history数组元素的代码:
createAction(action?) { return this.formBuilder.group({ title: [action ? action.title : undefined, [Validators.required, Validators.minLength(5), Validators.maxLength(50)]], description: [action ? action.description : undefined, [Validators.required, Validators.minLength(10), Validators.maxLength(200)]], resolveAt: [action ? action.resolveAt : undefined], result: [action ? action.response : undefined, [Validators.minLength(10), Validators.maxLength(200)]], resolvedAt: [action ? action.resolvedAt : undefined], status: [action ? action.status : undefined, [Validators.required]], relatedPerson: [action ? action.relatedPerson : undefined] }) } addAction(action?): void { const history = this.newTask.get('history') as FormArray; history.push(this.createAction(action || initialActionState)); } removeAction(i: number): void { const history = this.newTask.get('history') as FormArray; history.removeAt(i); }
提交时的代码(问题出在这里):
let cleanVar = {...this.newTask.value} // 也试过Object.assign({}, this.newTask.value),结果一样 cleanVar.history.forEach(item => { item.status = item.status.string; if (item.relatedPerson) item.relatedPerson = item.relatedPerson._id; }) console.log(this.newTask.value); // 发现原对象也被修改了!
原因分析:浅拷贝的坑!
兄弟,这个问题我之前也踩过!和Angular响应式表单本身没啥关系,核心是你用的浅拷贝搞的鬼😅
不管是扩展运算符{...obj}还是Object.assign({}, obj),都属于浅拷贝——它们只会复制对象的第一层属性,但对于嵌套的对象(比如你这里的history数组里的每一项),拷贝的其实是引用,而不是创建新的独立对象。
换句话说,cleanVar.history和this.newTask.value.history指向的是同一个数组,数组里的每个item也都是原表单value里对应对象的引用。所以当你修改cleanVar.history里的item.status或者item.relatedPerson时,本质上是在修改原对象的属性,自然会影响到this.newTask.value。
解决方案:用深拷贝搞定它
要解决这个问题,你需要对表单的value进行深拷贝,确保所有嵌套对象都被复制成独立的新对象。这里有几种常用的方法:
方法1:JSON序列化反序列化(最简单快捷)
这是最省事的方式,适合表单值里没有特殊类型(比如Date、Function)的场景:
let cleanVar = JSON.parse(JSON.stringify(this.newTask.value));
⚠️ 注意:如果你的表单里有Date对象,这种方法会把它转成字符串,需要后续手动转回来;如果有函数、undefined等,会被直接忽略。
方法2:用第三方库做深拷贝(最可靠)
如果需要处理特殊类型,推荐用Lodash的cloneDeep方法(需要先安装lodash:npm i lodash):
import cloneDeep from 'lodash/cloneDeep'; let cleanVar = cloneDeep(this.newTask.value);
这个方法能完美处理各种类型的嵌套对象,省心又靠谱。
方法3:手动遍历嵌套结构(自定义性最强)
如果你的表单结构固定,也可以手动遍历每一层进行拷贝,还能顺便完成修改,一举两得:
let cleanVar = {...this.newTask.value}; // 手动深拷贝history数组里的每一项,同时完成修改 cleanVar.history = cleanVar.history.map(item => ({ ...item, status: item.status.string, relatedPerson: item.relatedPerson ? item.relatedPerson._id : item.relatedPerson }));
这种方式不需要额外依赖,还能精准控制每一步的修改,适合有特定需求的场景。
内容的提问来源于stack exchange,提问作者Felipe Micali

