前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库React 嵌套对象不可变更新浅拷贝陷阱
ReReact状态与渲染

React 嵌套对象怎样不可变更新,为什么只写展开运算符还会共享旧引用?

展开运算符只复制当前一层,真正要保证的是旧版本的变更路径没有被原地改写。

前端进阶之旅 · 一题精讲更新于 2026.09.06
React#状态与渲染
先看核心答案读代码示例
理解线索

只重建真正变化的路径

  1. 新根对象向 React 提供新的状态身份
  2. 新父子对象逐层保护原有嵌套内容不被修改
  3. 共享未变分支保持没有变化的设置等对象引用

下面先检查浅拷贝共享事实,再构建正确的新版本,没有通过先污染原对象来制造结果。

核心回答

先记住这个答案

更新嵌套对象时,应沿根到目标字段的变更路径逐层创建新对象,未变化的分支可以保留原引用。只写 {...state} 会得到新的根对象,但内部 profile 等字段仍引用旧对象;随后修改内部字段会污染旧状态、其他共享引用与依赖引用比较的缓存。不可变更新不要求把整棵树全部深拷贝,而是让变化有新的身份、未变化部分保持结构共享。使用 useState 时若要基于前一状态修改,应在纯 updater 中完成这条路径的复制。

  • 浅拷贝不会自动复制嵌套对象
  • 沿变化路径创建新引用,未变分支可以共享
  • 深层频繁修改可考虑重构状态结构

一个新根对象并不能证明内部数据独立

个人资料包含姓名、地址和独立设置,修改地址城市时需要重建根、profile 和 address。settings 没有变化,可以继续使用原对象;这样既保留历史资料,也让依赖 settings 身份的逻辑避免看到无意义的新引用。

下面的身份比较说明每一层的变化。若把 shallow.profile.address.city 直接赋新值,改到的仍是 original 的地址;因此判断代码是否安全,不能只看第一行有没有出现展开运算符。

用引用比较核对变更路径JavaScript
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
内部对象仍可能共享引用,原地修改它们会同时改变旧版本可见的内容;必须逐层检查实际被修改的路径,而不只检查最外层。
不可变更新就是每次复制整个对象图
未变化分支可以结构共享,目标是保护历史内容与正确身份。无条件深拷贝会扩大工作量,也会破坏未变对象的引用稳定性。
试着用自己的话回答

面试官还会怎么问?

为什么复制数组后修改元素仍可能出问题?

数组复制保留元素引用,若元素本身是对象,修改它的字段仍会影响旧数组里的同一个对象。应替换目标元素对象而不是只换数组容器。

动态字段名能否直接放进展开对象?

可以使用计算属性,但要验证字段属于允许修改的范围,并明确它所在的层级。来自任意外部输入的路径不能直接当作可信更新指令。

怎样验证一次更新没有污染旧数据?

除核对新值外,还要断言旧值保持不变、变化路径身份不同、未变分支身份相同。这样的检查比只确认界面文字改变更能揭示共享引用问题。

从一道题,走向一组知识

把知识连起来

状态与渲染

React state 设为相同值会跳过什么,Object.is 有哪些容易误判的情况?

引用身份会影响 React 判断状态是否发生变化

状态与渲染

React 什么时候应该用函数式更新,异步任务计数为什么容易出错?

基于前一状态创建新结构时使用纯更新函数

参考资料

  • React:Updating Objects in State
  • React:Updating Arrays in State

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 一个新根对象并不能证明内部数据独立
  3. 放进 setter 时还要保护队列中的前一状态
  4. 嵌套过深时先看数据模型是否合适
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑