调用 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 进一步优化了性能,减少了不必要的渲染操作。

相关推荐
优雅的落幕2 小时前
前端---初识HTML(前端三剑客)
java·前端·javascript·css·html
nujnewnehc2 小时前
vue 少了2道面试题, vapor 来了后 vnode 和 diff 算法可以不需要了?
前端·vue.js·vapor
codingandsleeping3 小时前
前端工程化之webpack(万字)
前端·javascript
Jiude3 小时前
UnoCSS presetWind4() 背景色使用 color-mix() 的原因及解决方案
前端·css
无名之逆4 小时前
Hyperlane:Rust 生态中的轻量级高性能 HTTP 服务器库,助力现代 Web 开发
服务器·开发语言·前端·后端·http·面试·rust
范哥来了4 小时前
python web开发django库安装与使用
前端·python·django
烛阴4 小时前
JavaScript 的 “new Function”:你不知道的黑魔法,让代码更灵活!
前端·javascript
ConardLi4 小时前
发布第五天,我的开源项目突破 1.7 K Star!
前端·javascript·人工智能
Moment4 小时前
京东一面:postMessage 如何区分不同类型的消息 🤪🤪🤪
前端·javascript·面试
鱼樱前端4 小时前
🔥 Vue2 vs Vue3 的 h 函数终极指南:从入门到源码级深度解析
前端·vue.js