React 19 入门教程
不可变更新
本教程共 50 篇 · 第 21 篇 · 更新于 2026-07-29 · 约 7 分钟阅读
React不可变更新展开运算符Immer数组操作
21. 不可变更新
本节目标:理解不可变更新的原理,掌握对象和数组的不可变操作,学会用 Immer 简化复杂更新。
为什么要不可变
React 通过引用比较判断状态是否变化。直接修改原对象,引用不变,React 认为没变化,不重新渲染。
const obj = { count: 0 };
obj.count = 1; // 修改了内容,但引用没变
console.log(obj); // { count: 1 },但 React 不知道
正确做法是创建新对象:
const obj = { count: 0 };
const newObj = { ...obj, count: 1 }; // 新引用,React 能检测到
不可变不是性能差
现代 JS 引擎对浅拷贝做了优化。展开运算符只复制第一层,共享深层引用,开销很小。
对象的不可变操作
更新一个属性
setUser({
...user,
name: '张三'
});
更新多个属性
setUser({
...user,
name: '张三',
age: 25
});
删除属性
const { password, ...rest } = user;
setUser(rest); // 去掉 password 属性
条件更新
setUser({
...user,
...(shouldUpdate && { name: '李四' })
});
数组的不可变操作
添加元素
// 末尾添加
setItems([...items, newItem]);
// 开头添加
setItems([newItem, ...items]);
删除元素
// 按条件删除
setItems(items.filter(item => item.id !== id));
// 按索引删除
setItems(items.filter((_, i) => i !== index));
更新元素
// 按条件更新
setItems(items.map(item =>
item.id === id ? { ...item, done: true } : item
));
插入到指定位置
setItems([
...items.slice(0, index),
newItem,
...items.slice(index)
]);
排序和反转
// 排序:先复制再 sort(sort 是原生的)
setItems([...items].sort((a, b) => a.name.localeCompare(b.name)));
// 反转:先复制再 reverse
setItems([...items].reverse());
sort 和 reverse 会修改原数组
直接
items.sort()会改变原数组引用里的内容。先展开复制再操作。
嵌套数据的更新
嵌套越深,展开越痛苦:
// 更新 user.profile.avatar
setUser({
...user,
profile: {
...user.profile,
avatar: 'new-avatar.png'
}
});
三层以上嵌套,代码可读性急剧下降。
用 Immer 简化
Immer 让你用”直接修改”的语法,自动生成不可变结果:
import { useImmer } from 'use-immer';
function Form() {
const [user, updateUser] = useImmer({
name: '',
profile: {
avatar: '',
bio: ''
}
});
function handleBioChange(e) {
// 看起来像直接修改,实际是不可变更新
updateUser(draft => {
draft.profile.bio = e.target.value;
});
}
}
什么时候用 Immer
嵌套超过两层、数组操作复杂时,Immer 能显著提升可读性。简单场景用展开运算符就行。
不可变更新速查表
| 操作 | 对象 | 数组 |
|---|---|---|
| 添加 | { ...obj, key: val } | [...arr, item] |
| 删除 | delete + 展开 | arr.filter(...) |
| 更新 | { ...obj, key: newVal } | arr.map(...) |
| 深层 | 逐层展开 | 逐层展开 |
常见错误
// 错误:直接修改
items.push(newItem);
user.name = '张三';
// 错误:sort/reverse 不复制
items.sort();
// 正确
setItems([...items, newItem]);
setUser({ ...user, name: '张三' });
setItems([...items].sort());
本节要点
- React 靠引用比较检测变化,必须创建新对象/数组
- 对象用展开运算符,数组用 map/filter/concat
- sort/reverse 前先复制
- 嵌套太深考虑 Immer