react - useState更新机制(直接更新和函数式更新)

文章目录

1、直接更新

每当函数内部访问外部作用域的变量时,就会形成闭包;

ts 复制代码
const [count, setCount] = useState(0);
const handleMultipleUpdates = () => {
  //   这里访问了外部作用域的 count 变量;形成了闭包。
  setCount(count + 1); // 计划更新为 1
  setCount(count + 1); // 再次计划更新为 1
  setCount(count + 1); // 又一次计划更新为 1
};

函数在定义的时候,它已经"记住"(捕获)了当前渲染周期中的 count 值也就是初始值0 ;所以上面的代码等同于下面的:

ts 复制代码
const handleMultipleUpdates = () => {
  setCount(0 + 1);
  setCount(0 + 1);
  setCount(0 + 1);
};

所以当react更新队列依次执行这个三个setCount;值都是 1;所以在这个事件中count的值始终是 1;

但是这里还有另外一个影响因素需要讨论。React 会等到事件处理函数中的 所有 代码都运行完毕再处理你的 state 更新。 这就是重新渲染只会发生在所有这些 setNumber()调用 之后 的原因。这种特性也就是 批处理

2、函数式更新

ts 复制代码
const handleMultipleUpdates = () => {
  setCount((prevCount) => prevCount + 1);
  setCount((prevCount) => prevCount + 1);
  setCount((prevCount) => prevCount + 1);
};

使用函数式更新可以拿到最新变动的值,符合我们代码所表现出来的预期结果。

使用setCount(prev => prev + 1)函数式更新会使用最新变动的值做计算。因为:参数注入

1,React 会在处理更新时自动将最新状态 作为 prev 参数传入

2,不依赖外部作用域的变量

直接更新和函数式更新对比

特性 setCount(count + 1) setCount(prev => prev + 1)
依赖值 依赖当前闭包中的 count 依赖 React 提供的上一个状态值
多次调用的结果 所有调用都基于同一个初始值 每次调用都基于前一次更新后的值
更新队列处理 批量更新但使用相同的基础值 批量更新但依次传递最新值
闭包问题 受闭包影响,获取的是当前渲染周期的值 不受闭包影响,总能获取最新值
适用场景 简单独立的状态更新 连续依赖前一次更新的状态变更

3、异步更新注意

ts 复制代码
const [count, setCount] = useState(0);
const handleAsyncUpdate = () => {
  setCount(count + 5);
  console.log("立即获取count:", count); // 0
  setTimeout(() => {
    console.log("定时器里面的count:", count); // 0
  }, 0);
};

这里面存在的问题是:为什么 setTimeout 里面获取的也是 count 的旧值,因为 setTimeout 函数也形成了闭包,所以闭包值在函数创建时就确定了,不会自动更新;依旧是 0;

那该怎么解决呢?

解决方案: 在函数式更新中获取

ts 复制代码
const handleAsyncUpdate = () => {
  setCount(count + 5);
  setTimeout(() => {
    setCount((prev) => {
      console.log("函数式更新中的count:", prev); // 5 和视图保持一致
      return prev;
    });
  }, 0);
};

4、批量更新机制

批量更新(Batching)是 React 优化性能的核心机制之一,它通过合并多个状态更新来减少不必要的渲染。

ts 复制代码
// 演示批量更新
const handleBatchUpdates = () => {
  setCount(count + 2);
  setText("Updated Text");
};

React 会自动批量处理这些更新,只触发一次渲染

5、更新复杂数据类型(对象和数组)

React 的状态更新遵循不可变数据原则。React 通过浅比较(shallow comparison)来判断状态是否变化:

1,对于基本类型(string, number, boolean 等),比较值本身

2,对于引用类型(object, array 等),比较内存引用地址

ts 复制代码
const [user, setUser] = useState({ name: "Alice", age: 25 });

// 直接修改(错误)
user.age = 26; // 内存地址不变
setUser(user); // React认为"没有变化",不会触发重新渲染

// 正确做法
setUser({ ...user, age: 26 }); // 创建新对象,新内存地址

5.1 深层对象属性更新

js 复制代码
const [data, setData] = useState({
  user: {
    name: "Alice",
    profile: {
      level: 1
    }
  }
});
// 更新深层属性
setData({
  ...data,
  user: {
    ...data.user,
    profile: {
      ...data.user.profile,
      level: 2
    }
  }
});

5.2 更新数组操作

js 复制代码
const [items, setItems] = useState(["a", "b", "c"]);

// 添加元素
setItems([...items, "d"]);

// 删除第二个元素
setItems(items.filter((item, index) => index !== 1));

// 更新元素
setItems(items.map((item, index) => (index === 1 ? "new" : item)));

// 排序
setItems([...items].sort()); // 先创建副本再排序

5.3 复杂操作示例

js 复制代码
// 所操作的数据源
const [todos, setTodos] = useState([
  { id: 1, text: "Learn React", done: false },
  { id: 2, text: "Build app", done: false }
]);

// 切换完成状态
setTodos(todos.map((todo) => (todo.id === 1 ? { ...todo, done: !todo.done } : todo)));

// 删除项目
setTodos(todos.filter((todo) => todo.id !== 2));

// 替换索引为2的元素
const [items, setItems] = useState(["a", "b", "c", "d"]);
setItems((prevItems) => prevItems.map((item, index) => (index === 2 ? "new" : item)));

// 替换特定ID的对象
setTodos(todos.map((todo) => (todo.id === 2 ? { ...todo, text: "Build awesome app" } : todo)));

// 在特定位置插入元素
const [items, setItems] = useState(["a", "b", "d"]);
// 在索引2处插入'c' (插入到'b'和'd'之间)
setItems([
  ...items.slice(0, 2), // 取前两个元素
  "c", // 插入新元素
  ...items.slice(2) // 剩余元素
]);
// 更安全的实现方式
function insertItem(array, index, newItem) {
  return [...array.slice(0, index), newItem, ...array.slice(index)];
}
setItems(insertItem(items, 2, "c"));

5.4 为什么 React 选择浅比较?

为什么 React 选择浅比较?

  1. 性能考量:深比较对大型对象/数组开销过大
  2. 明确性:强制开发者显式声明状态变更
  3. 可预测性:避免深比较可能导致的意外行为
  4. 与不可变性配合:鼓励使用不可变数据模型
特性 浅比较 深比较
比较深度 只比较第一层 递归比较所有嵌套属性
性能 高效(O(1)或 O(n)简单遍历) 低效(O(n)复杂递归)
React 使用 默认采用 几乎不使用
适用场景 频繁比较的大数据结构 需要精确知道所有变化的特殊场景

👉点击进入 我的网站

相关推荐
天天向上vir2 小时前
防抖与节流
前端·typescript·vue
宇珩前端踩坑日记2 小时前
怎么让 Vue DevTools 用 Trae 打开源码
前端·trae
小徐不会敲代码~2 小时前
Vue3 学习 6
开发语言·前端·vue.js·学习
CreasyChan2 小时前
C#中单个下划线的语法与用途详解
前端·c#
舆通Geo优化2 小时前
2025年GEO优化选哪家好?长沙GEO优化公司排名:GEO服务商哪家靠谱?
javascript·css·html
GDAL2 小时前
Tailwind CSS 菜单实现全面讲解教程(基于书签篮网站场景)
前端·css·菜单
m5655bj2 小时前
如何通过 C# 实现 PDF 页面裁剪
前端·pdf·c#
这是个栗子2 小时前
前端开发中的常用工具函数(持续更新中...)
前端·javascript·算法
zhangsansecond2 小时前
vs创建 基于ASP.NET Framework 的 SOAP 协议 Web 服务,https无法访问
前端·https·asp.net