如何正确克隆vue-apollo中GraphQL返回的只读对象以支持表单编辑
核心问题原因
你之前使用Object.assign做克隆失效、触发只读报错,本质是两个问题:
Object.assign是浅拷贝,仅会拷贝对象第一层的原始类型属性,所有嵌套的引用类型(数组、嵌套对象)都会直接沿用源对象的引用,不会生成新副本。你外层哪怕生成了新对象,内部嵌套的regulations数组、数组内的每一项对象本质还是Apollo缓存里被冻结的只读数据,修改时自然触发只读报错。- 你之前尝试对
regulations数组用Object.assign做合并的写法本身就有逻辑问题:Object.assign是针对普通键值对对象的合并方法,把两个数组传给它做合并,只会把数组的下标作为键做合并,根本不会遍历数组元素生成独立的可编辑副本,完全达不到克隆的效果。
补充说明:Apollo客户端默认会对缓存中的查询结果做对象冻结处理,这是它的内置设计,目的是避免直接修改源缓存导致的状态不一致,因此必须生成完全脱离缓存引用的本地副本才能做编辑操作。
正确的克隆实现方式
只要生成和原只读对象完全无引用关联的全新可编辑对象即可,常用的实现方式:
方式1:逐层级手动拷贝(最可控,适合结构简单的场景)
针对你给出的返回结构,直接对每一层嵌套结构做独立副本生成,不要依赖浅拷贝方法:
// 基于查询结果生成完全独立的可编辑表单对象 const editableForm = { // 原始类型的id直接赋值即可,不存在引用问题 id: result.id, // 遍历regulations数组,对每一项元素都生成全新的独立对象,不保留原引用 regulations: result.regulations.map(item => ({ title: item.title, approved: item.approved })) }
这种方式的好处是你可以完全控制哪些字段需要同步到可编辑状态,避免把不需要的字段也拷贝进去,后续提交Mutation的时候可以直接使用这个对象上修改后的字段值。
方式2:使用原生结构化深拷贝(适合复杂嵌套结构)
如果你的返回结构层级很深,手动逐字段写拷贝太繁琐,可以直接使用浏览器原生支持的structuredClone方法,一次性生成和源对象完全无引用关联的全新可编辑副本:
// 生成完全独立的深拷贝对象,没有任何原只读对象的引用关联 const editableForm = structuredClone(result)
这种方法兼容现代所有主流浏览器,不会像JSON.parse(JSON.stringify(result))那样丢失日期、undefined等特殊类型的字段,拷出来的对象完全可读写,不会触发只读报错。
注意事项
- 不要直接把Apollo返回的原始result对象绑定到表单上,所有编辑操作都要基于上面生成的
editableForm副本做修改,避免修改被冻结的只读对象触发报错。 - 不要用固定结构的模板去硬合并动态长度的数组数据,像你之前写的固定单元素的
regulations模板,遇到返回多条法规数据的场景,会出现数组长度不匹配的问题,直接通过map遍历返回的数组生成长度一致的副本更可靠。 - 提交GraphQL变更请求时,直接从
editableForm中取出修改后的字段值传入mutation接口即可,不需要额外做转换。 - 如果使用Vue2做开发,记得把生成的
editableForm赋值到组件的响应式data字段上,确保表单的双向绑定能正常生效,避免把对象存在非响应式的临时变量上导致绑定失效。
内容的提问来源于stack exchange,提问作者capiono
相关产品推荐
相关产品推荐

