首页 / React 19 入门教程 / 不可变更新

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