调用 useState 之后发生了什么

在 React 中,useState 是一个 Hook,用于在函数组件中添加和管理状态。当你调用 useState 时,React 会执行一系列步骤来管理状态并确保组件能够正确地重新渲染。以下是详细的解释:

1. 状态初始化

首次渲染:
  • 创建新的状态值 :当你首次渲染组件时,useState(initialValue) 会创建一个新的状态值,并将其存储在 React 内部管理的 Hooks 链表中。
  • 函数形式的初始值 :如果 initialValue 是一个函数(如 useState(() => 0)),React 会在首次渲染时执行该函数获取初始值(仅在首次渲染时执行一次)。
jsx 复制代码
const [count, setCount] = useState(0); // 初始值为 0
// 或者使用函数形式的初始值
const [count, setCount] = useState(() => computeInitialValue());
后续渲染:
  • 直接返回已存储的状态值 :组件重新渲染时,useState 会直接返回链表中已存储的当前状态值,而不会重新初始化。

2. 返回状态与更新函数

useState 返回一个数组,包含两个元素:

  • 当前状态值:从 Hooks 链表中读取的值。
  • 状态更新函数(如 setCount :用于触发状态变更和组件的重新渲染。
jsx 复制代码
const [value, setValue] = useState('初始值');

3. 更新状态与触发渲染

状态变更检查:
  • 当你调用 setValue(newValue) 时,React 会比较新旧值(使用 Object.is 算法)。如果值未变化,不会触发重新渲染。
标记组件为待更新:
  • 如果值发生变化,React 会将组件标记为需要重新渲染,并将其加入更新队列。
批量更新(Batching):
  • 在 React 18+ 中,所有更新(包括事件回调、setTimeout 等)默认自动批处理,合并为一次渲染。
  • 多次连续调用 setValue 可能被合并,例如:
jsx 复制代码
setValue(1);
setValue(2); // 最终直接更新为 2,仅触发一次渲染。
渲染阶段:
  • 在下一次渲染周期中,React 重新执行组件函数,此时 useState 返回最新的状态值。

4. 触发调和过程 (Reconciliation)

调和过程:
  • 在重新渲染过程中,React 生成一个新的虚拟 DOM 树,并将其与之前的虚拟 DOM 树进行比较。
  • 这个过程称为"调和"(Reconciliation),其目的是高效地计算出哪些部分需要更新。
差异计算:
  • React 使用一种称为"差异算法"(Diffing Algorithm)的技术来高效地计算新旧虚拟 DOM 树之间的差异。
  • 通过这种算法,React 可以精确地知道哪些节点发生了变化以及应该如何更新这些节点,从而实现最小化重渲染。
最小化重渲染:
  • 基于差异计算的结果,React 只对那些实际发生变化的部分进行更新,而不是整个页面。
  • 这种按需更新的方式可以显著提高性能,特别是在大型应用中。

具体步骤详解

  1. 状态初始化与存储

    • 首次渲染时,useState(initialValue) 初始化状态,并将其存储在 React 的内部 Hooks 链表中。
    • 如果 initialValue 是一个函数,React 会在首次渲染时执行该函数获取初始值。
  2. 返回状态与更新函数

    • useState 返回一个数组 [currentState, setState],其中 currentState 是当前状态值,setState 是更新状态的函数。
  3. 调用 setState 更新状态

    • 当你调用 setState(newValue) 时,React 会将新的状态值放入更新队列中,并在合适的时机批量处理这些更新。
    • React 使用 Object.is 算法比较新旧状态值,如果值未变化,则不会触发重新渲染。
  4. 调度更新

    • React 不会立即执行状态更新和重新渲染,而是将这些更新请求放入队列,并在合适的时机批量处理它们。
    • 在 React 18+ 中,默认启用了批处理(Batching),这意味着多次连续的状态更新可能会被合并为一次渲染。
  5. 触发调和过程

    • React 生成新的虚拟 DOM 树,反映最新的 UI 状态。
    • React 比较新旧虚拟 DOM 树之间的差异,确定哪些部分需要更新。
  6. 差异计算与最小化重渲染

    • React 计算新旧虚拟 DOM 树之间的差异,基于差异计算的结果,React 只对那些实际发生变化的部分进行更新,而不是整个页面。

以下是一个完整的示例,展示了如何使用 useState 并解释其背后的流程:

jsx 复制代码
import React, { useState } from "react";
import { Button } from "antd";

const DateModule = () => {
  // 使用 useState 钩子初始化状态
  const [value, setValue] = useState('初始值');

  return (
    <>
      {/* 显示当前的 value */}
      <p>{value}</p>
      
      {/* 按钮点击时调用 setValue 更新 value */}
      <Button onClick={() => setValue('new Value')}>按钮</Button>
    </>
  );
};

export default DateModule;

总结

调用 useState 并更新状态后,React 会经历以下几个关键步骤:

  1. 状态初始化 :首次渲染时,useState 创建并存储状态值;后续渲染时,直接返回存储的状态值。
  2. 返回状态与更新函数useState 返回当前状态值和更新状态的函数。
  3. 更新状态与调度 :调用更新函数(如 setValue)时,React 将新的状态值放入更新队列,并在合适的时机批量处理这些更新。
  4. 触发调和过程:React 生成新的虚拟 DOM 树,并准备重新渲染。
  5. 差异计算与最小化重渲染:React 计算新旧虚拟 DOM 树之间的差异,并只对那些实际发生变化的部分进行更新。

通过这种方式,React 实现了高效的 UI 更新,确保应用在状态变化时能够快速响应且保持高性能。同时,通过批处理和异步更新机制,React 进一步优化了性能,减少了不必要的渲染操作。

相关推荐
Hilaku4 分钟前
JavaScript 里的 !0、!1 到底是啥?聊聊那些压缩器最爱的“极简写法”
前端·javascript
全栈陈序员13 分钟前
前端文件下载常用方式详解
前端·javascript·chrome·ajax·css3·html5·safari
二十一_22 分钟前
🤖✨ ChatGPT API深度体验:让AI看懂图片、听懂语音、调用你的代码
前端·chatgpt·openai
Developer_Niuge28 分钟前
前端批量请求失败重复弹窗的正确解决方案
前端
前端小饭桌29 分钟前
告别嵌套地狱:用数据结构优化解决 JS 多层循环的混乱与静默错误
前端·javascript
爱摸鱼的格子30 分钟前
🚀 你真的会用 Promise.all 吗?10 个实用技巧助你成为异步处理大师!
前端
JacksonGao31 分钟前
React Fiber的调度算法你了解多少呢?
前端·react.js
这可不简单33 分钟前
方便易懂的自适应方案---echarts和dom样式大小自适应
前端·vue.js·echarts
玲小珑35 分钟前
Auto.js 入门指南(七)定时任务调度
android·前端
橘黄的猫36 分钟前
深入解析 import.meta.url:与 new URL() 的关系及 Vite 中的 base 路径影响
前端·vite