先记住这个答案
更新嵌套对象时,应沿根到目标字段的变更路径逐层创建新对象,未变化的分支可以保留原引用。只写 {...state} 会得到新的根对象,但内部 profile 等字段仍引用旧对象;随后修改内部字段会污染旧状态、其他共享引用与依赖引用比较的缓存。不可变更新不要求把整棵树全部深拷贝,而是让变化有新的身份、未变化部分保持结构共享。使用 useState 时若要基于前一状态修改,应在纯 updater 中完成这条路径的复制。
- 浅拷贝不会自动复制嵌套对象
- 沿变化路径创建新引用,未变分支可以共享
- 深层频繁修改可考虑重构状态结构
一个新根对象并不能证明内部数据独立
个人资料包含姓名、地址和独立设置,修改地址城市时需要重建根、profile 和 address。settings 没有变化,可以继续使用原对象;这样既保留历史资料,也让依赖 settings 身份的逻辑避免看到无意义的新引用。
下面的身份比较说明每一层的变化。若把 shallow.profile.address.city 直接赋新值,改到的仍是 original 的地址;因此判断代码是否安全,不能只看第一行有没有出现展开运算符。
const original = {
profile: { name: 'Lin', address: { city: '杭州' } },
settings: { theme: 'dark' }
};
const shallow = { ...original };
console.log(shallow.profile === original.profile);
const next = {
...original,
profile: {
...original.profile,
address: { ...original.profile.address, city: '成都' }
}
};
console.log(next === original, next.profile === original.profile);
console.log(next.profile.address === original.profile.address);
console.log(next.settings === original.settings);
console.log(original.profile.address.city, next.profile.address.city);查看输出与解释
true
false false
false
true
杭州 成都浅拷贝后的 profile 仍共享旧引用;正确更新的新根、profile 和 address 都有新身份,未变的 settings 保持共享,原城市仍为杭州而新城市为成都。
放进 setter 时还要保护队列中的前一状态
事件处理器可以使用 setProfile(previous => 新对象),在 updater 内从 previous 的各层取出未变字段。若函数先修改 previous.profile 再返回新根,仍然违反不可变要求;函数式语法本身不会替你复制对象。
对象在内存中是引用关系,两个字段甚至两个组件可能指向同一个地址对象。沿某一路径替换地址会让该路径获得新版本,其他引用保留原内容;如果业务确实要求多个实体同步变化,应通过明确标识和统一数据来源表达关系。
嵌套过深时先看数据模型是否合适
频繁写多层展开会增加漏复制某一层的概率。可以把实体按标识保存、减少冗余嵌套,或使用能生成不可变结果的工具;但仍要理解输入和输出的身份关系,不能把工具当作无需审查更新语义的理由。
数组元素也是对象,复制数组不会自动复制内部元素。只更新某个成员时应创建新数组并替换目标对象,保留其他元素身份。全量深拷贝可能增加时间与内存开销,并使未变分支也获得新引用,不应作为每次状态更新的默认方案。
容易答错的地方
- 返回了新的根对象就不会影响旧 state
- 内部对象仍可能共享引用,原地修改它们会同时改变旧版本可见的内容;必须逐层检查实际被修改的路径,而不只检查最外层。
- 不可变更新就是每次复制整个对象图
- 未变化分支可以结构共享,目标是保护历史内容与正确身份。无条件深拷贝会扩大工作量,也会破坏未变对象的引用稳定性。
面试官还会怎么问?
为什么复制数组后修改元素仍可能出问题?
数组复制保留元素引用,若元素本身是对象,修改它的字段仍会影响旧数组里的同一个对象。应替换目标元素对象而不是只换数组容器。
动态字段名能否直接放进展开对象?
可以使用计算属性,但要验证字段属于允许修改的范围,并明确它所在的层级。来自任意外部输入的路径不能直接当作可信更新指令。
怎样验证一次更新没有污染旧数据?
除核对新值外,还要断言旧值保持不变、变化路径身份不同、未变分支身份相同。这样的检查比只确认界面文字改变更能揭示共享引用问题。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。