浅拷贝仅复制第一层引用,更新嵌套对象需逐层展开或用Immer等工具;数组更新须避免原地方法,坚持返回新数组。

在 React 中,保持状态对象的不可变性是避免意外渲染和状态 bug 的关键。浅拷贝本身不能完全保证不可变性,但它是在更新嵌套对象时最常用、最轻量的起点——重点在于“只拷贝第一层”,并配合合理更新策略。
浅拷贝只是第一步,不是终点
浅拷贝(如 Object.assign({}, obj)、展开运算符 {...obj} 或 Array.from(arr))只复制对象自身属性的引用,不递归处理嵌套对象。这意味着:修改拷贝后对象的顶层属性是安全的;但若该属性本身是对象或数组,直接修改它内部的值仍会污染原始数据。
- ✅ 安全操作:
const newState = {...state}; newState.count = newState.count + 1; - ❌ 危险操作:
const newState = {...state}; newState.user.name = "Alice"; // 原 state.user.name 也被改了
更新嵌套对象时,必须逐层浅拷贝
要安全修改深层属性(如 state.user.profile.avatar),需对每一级需要变更的路径做浅拷贝,形成一条“新引用链”。
- 用展开运算符手动构造:
const newState = {<br> ...state,<br> user: {<br> ...state.user,<br> profile: {<br> ...state.user.profile,<br> avatar: "new-url.jpg"<br> }<br> }<br>}; - 函数式封装提升可读性:
const updateAvatar = (state, url) => ({<br> ...state,<br> user: { ...state.user, profile: { ...state.user.profile, avatar: url } }<br>});
用工具库简化深层更新(推荐用于复杂结构)
当嵌套层级深或更新逻辑频繁时,手动展开易出错。可借助成熟工具减少样板代码:
-
Immer:写法接近“直接修改”,底层自动产出不可变副本。
import { produce } from 'immer';<br>const newState = produce(state, draft => { draft.user.profile.avatar = "new-url.jpg"; }); -
lodash/fp(函数式版本):
import { set } from 'lodash/fp';<br>const newState = set(['user', 'profile', 'avatar'], 'new-url.jpg', state);
数组更新也要注意浅拷贝语义
数组是对象,[...arr] 或 arr.slice() 是安全的浅拷贝方式;但 arr.push()、arr.splice() 等原地方法会改变原数组,必须避免。
- ✅ 正确:
const newArray = [...state.items, newItem];或state.items.map(...) - ❌ 错误:
state.items.push(newItem); setState({ items: state.items }); - 更新某一项(如第 2 个):
const updated = state.items.map((item, i) => i === 1 ? {...item, done: true} : item);
不复杂但容易忽略:浅拷贝不是银弹,它的价值在于配合“不直接修改旧对象”的纪律。真正守住不可变性,靠的是意识+模式+合适工具,而不是某一行拷贝代码本身。


















