现代前端开发推荐不可变数组操作,因其能确保React等框架通过引用变化触发更新、提升调试可预测性、支持撤销/重做与并发安全,并获Immer等工具链优化支持。

现代数组操作越来越强调“不可变”——不是禁止修改,而是要求每次变更都生成新数组,而非就地改动原数组。这个趋势不是 React 独创的,而是由框架更新机制、调试可预测性、协作安全性共同推动的结果。
React 的浅比较机制是直接动因
React 不深查数组内容是否变化,只看引用地址是否不同。一旦你用 push、splice、arr[i] = x 这类方式操作数组,原始引用没变,React 就认为“状态没更新”,组件不会重渲染。
- 错误写法:
list.push(newItem); setList(list)→ list 引用未变,UI 停滞 - 正确写法:
setList([...list, newItem])或setList(list.concat(newItem))→ 新引用触发更新
状态管理工具强化了这一范式
Redux 要求 reducer 必须返回新状态;Zustand、Jotai 等库默认鼓励函数式更新;即使是 Vue3 的响应式系统,虽然支持 push 等方法,但官方文档仍推荐展开语法或 toRef 配合不可变逻辑,以保持跨团队协作的一致性。
- Redux 示例:
return { ...state, items: [...state.items, newItem] } - Zustand 示例:
set(state => ({ items: [...state.items, newItem] }))
不可变操作带来额外工程收益
它不只是为了“让 React 工作”,更支撑了真实开发中的关键能力:
- 撤销/重做:每次更新都存一个快照,回退只需取历史引用
- 时间切片调试:DevTools 可精确对比两个状态快照的差异
- 并发安全:多个异步操作不会相互覆盖(比如两个 API 同时更新同一数组)
- 类型友好:TypeScript 更容易推导 readonly 数组、tuple 类型,避免运行时赋值错误
工具链正在降低使用门槛
纯手动写展开、filter、map 容易出错且冗长。因此 Immer 成为事实标准——你写 draft.list.push(x),它自动产出不可变新状态。类似地,Lodash/fp、Remeda 等函数式库也默认返回新数据,不修改入参。
- Immer 示例:
produce(state, draft => { draft.list.push(newItem); }) - Remeda 示例:
R.append(newItem, list)(非破坏性)


















