React Hooks 的工作原理
字数 2310 2025-11-02 17:10:18

React Hooks 的工作原理

描述
React Hooks 是 React 16.8 引入的革命性特性,它允许函数组件使用状态(state)和其他 React 特性(如生命周期)。理解其工作原理,关键在于搞明白:当一个函数组件被调用后,其执行上下文就结束了,那么 Hook(如 useState)是如何在多次渲染之间保持并关联到正确的状态的呢?

解题过程

  1. 核心问题:状态与组件的关联

    • 对于类组件,状态(this.state)是存储在组件实例(this)上的。实例在组件的整个生命周期内都存在,因此状态得以保存。
    • 对于函数组件,它只是一个函数。每次渲染(或重新渲染)都是一次全新的函数调用。函数执行完毕后,其内部的局部变量通常会被销毁。那么,useState 返回的 state 必须存储在函数组件之外的一个地方,并且需要一种机制将它和特定的组件函数、特定的 Hook 调用关联起来。
  2. 答案:React 的维护机制

    • React 内部维护了一个通用的数据结构,用于存储所有组件的状态、副作用等信息。对于函数组件,React 使用一种名为 "Fiber" 的数据结构来代表一个工作单元。每个函数组件都对应一个 Fiber 节点。
    • 在 Fiber 节点中,有一个属性叫做 memoizedState。它不是一个单一的值,而是一个链表(linked list) 的数据结构。
  3. Hooks 的链表结构

    • 当你在一个函数组件中调用多个 Hooks(例如,两个 useState 和一个 useEffect)时,React 并不会用变量名来区分它们,而是严格按照它们在组件中被调用的顺序来记录。
    • 在组件第一次渲染(mount)时:
      • React 会为这个组件的 Fiber 节点初始化一个 memoizedState 链表,这个链表是空的。
      • 当执行到第一个 Hook(如 const [name, setName] = useState('Alice'))时,React 会创建一个代表这个 Hook 的对象(通常称为 "Hook object")。这个对象包含了状态值(例如 'Alice')、更新状态的队列(dispatch)等信息。
      • React 将这个 Hook 对象作为链表的第一个节点,挂载到 memoizedState 上。
      • 当执行到第二个 Hook(如 const [age, setAge] = useState(30))时,React 会创建第二个 Hook 对象,并将其作为链表的第二个节点,链接到第一个节点之后。
      • 以此类推。useEffect, useCallback 等其他 Hooks 也是以同样的方式,按照调用顺序被添加到链表中。
    • 在组件后续的更新渲染(update)时:
      • React 会再次执行这个函数组件。
      • 当它再次遇到 useState 时,它不会创建一个新的 Hook 对象。相反,它会顺着这个组件 Fiber 节点上的 Hook 链表,按顺序逐个取出之前对应的 Hook 对象,并返回其中存储的当前状态值。
      • 这个过程完全依赖于调用顺序。如果第二次渲染时 Hook 的调用顺序和第一次不同,React 顺着链表去找,就会把第一个 Hook 的状态错误地关联到第二个 Hook 上,导致 bug。
  4. 为什么 Hook 调用顺序不能变(Hooks Rules)

    • 基于上面的原理,就很容易理解 React 官方提出的规则:"只在最顶层使用 Hook""不要在循环、条件或嵌套函数中调用 Hook"
    • 举例说明:假设我们有如下代码:
      function MyComponent({ isVisible }) {
        if (isVisible) {
          const [name, setName] = useState('Alice'); // 第一个Hook
        }
        const [age, setAge] = useState(30); // 第二个Hook
      }
      
      • 第一次渲染isVisibletrue
        • 执行顺序:Hook1 (useState('Alice')) -> Hook2 (useState(30))。
        • React 内部链表:[Hook1(name), Hook2(age)]
      • 第二次渲染isVisible 变为 false
        • 执行顺序:直接跳过 if 块,执行 Hook2 (useState(30))。
        • React 内部会做什么?它会顺着链表按顺序取:它期望第一个 Hook 是 useState('Alice'),但实际上这次执行第一个遇到的 Hook 是 useState(30)。React 会错误地将链表节点1(存储着 'Alice')的状态返回给这次调用的第一个 Hook(原本应该是 age),导致严重的数据错乱。
  5. 状态更新与触发重新渲染

    • 当您调用由 useState 返回的 setter 函数(如 setName('Bob'))时,React 会将更新动作(action,即 'Bob')放入该 Hook 对象的更新队列中。
    • 然后,React 会调度一次新的渲染。
    • 在新的渲染中,组件函数被再次执行。当执行到 useState 时,React 会从链表中找到对应的 Hook 对象,计算其更新队列中的最新状态值,并将这个新值返回给你。这样,组件就使用新的状态渲染了 UI。

总结
React Hooks 的工作原理核心是:React 在组件的 Fiber 节点上通过一个链表来顺序存储所有 Hook 的状态和信息。组件每次渲染时,React 都通过遍历这个链表,按固定顺序来提供对应的状态或执行对应的副作用。 这完美解释了为什么 Hooks 能让函数具备"记忆"能力,也解释了为什么必须保证 Hook 调用顺序的稳定性。

React Hooks 的工作原理 描述 React Hooks 是 React 16.8 引入的革命性特性,它允许函数组件使用状态(state)和其他 React 特性(如生命周期)。理解其工作原理,关键在于搞明白:当一个函数组件被调用后,其执行上下文就结束了,那么 Hook(如 useState )是如何在多次渲染之间保持并关联到正确的状态的呢? 解题过程 核心问题:状态与组件的关联 对于类组件,状态( this.state )是存储在组件实例( this )上的。实例在组件的整个生命周期内都存在,因此状态得以保存。 对于函数组件,它只是一个函数。每次渲染(或重新渲染)都是一次全新的函数调用。函数执行完毕后,其内部的局部变量通常会被销毁。那么, useState 返回的 state 必须存储在函数组件之外的一个地方,并且需要一种机制将它和特定的组件函数、特定的 Hook 调用关联起来。 答案:React 的维护机制 React 内部维护了一个通用的数据结构,用于存储所有组件的状态、副作用等信息。对于函数组件,React 使用一种名为 "Fiber" 的数据结构来代表一个工作单元。每个函数组件都对应一个 Fiber 节点。 在 Fiber 节点中,有一个属性叫做 memoizedState 。它不是一个单一的值,而是一个 链表(linked list) 的数据结构。 Hooks 的链表结构 当你在一个函数组件中调用多个 Hooks(例如,两个 useState 和一个 useEffect )时,React 并不会用变量名来区分它们,而是严格按照它们在组件中 被调用的顺序 来记录。 在组件第一次渲染(mount)时: React 会为这个组件的 Fiber 节点初始化一个 memoizedState 链表,这个链表是空的。 当执行到第一个 Hook(如 const [name, setName] = useState('Alice') )时,React 会创建一个代表这个 Hook 的对象(通常称为 "Hook object")。这个对象包含了状态值(例如 'Alice' )、更新状态的队列(dispatch)等信息。 React 将这个 Hook 对象作为链表的第一个节点,挂载到 memoizedState 上。 当执行到第二个 Hook(如 const [age, setAge] = useState(30) )时,React 会创建第二个 Hook 对象,并将其作为链表的第二个节点,链接到第一个节点之后。 以此类推。 useEffect , useCallback 等其他 Hooks 也是以同样的方式,按照调用顺序被添加到链表中。 在组件后续的更新渲染(update)时: React 会再次执行这个函数组件。 当它再次遇到 useState 时,它不会创建一个新的 Hook 对象。相反,它会顺着这个组件 Fiber 节点上的 Hook 链表,按顺序 逐个取出 之前对应的 Hook 对象,并返回其中存储的当前状态值。 这个过程完全依赖于 调用顺序 。如果第二次渲染时 Hook 的调用顺序和第一次不同,React 顺着链表去找,就会把第一个 Hook 的状态错误地关联到第二个 Hook 上,导致 bug。 为什么 Hook 调用顺序不能变(Hooks Rules) 基于上面的原理,就很容易理解 React 官方提出的规则: "只在最顶层使用 Hook" 和 "不要在循环、条件或嵌套函数中调用 Hook" 。 举例说明 :假设我们有如下代码: 第一次渲染 : isVisible 为 true 。 执行顺序:Hook1 ( useState('Alice') ) -> Hook2 ( useState(30) )。 React 内部链表: [Hook1(name), Hook2(age)] 。 第二次渲染 : isVisible 变为 false 。 执行顺序:直接跳过 if 块,执行 Hook2 ( useState(30) )。 React 内部会做什么?它会顺着链表按顺序取:它期望第一个 Hook 是 useState('Alice') ,但实际上这次执行第一个遇到的 Hook 是 useState(30) 。React 会错误地将链表节点1(存储着 'Alice' )的状态返回给这次调用的第一个 Hook(原本应该是 age ),导致严重的数据错乱。 状态更新与触发重新渲染 当您调用由 useState 返回的 setter 函数(如 setName('Bob') )时,React 会将更新动作(action,即 'Bob' )放入该 Hook 对象的更新队列中。 然后,React 会调度一次新的渲染。 在新的渲染中,组件函数被再次执行。当执行到 useState 时,React 会从链表中找到对应的 Hook 对象,计算其更新队列中的最新状态值,并将这个新值返回给你。这样,组件就使用新的状态渲染了 UI。 总结 React Hooks 的工作原理核心是: React 在组件的 Fiber 节点上通过一个链表来顺序存储所有 Hook 的状态和信息。组件每次渲染时,React 都通过遍历这个链表,按固定顺序来提供对应的状态或执行对应的副作用。 这完美解释了为什么 Hooks 能让函数具备"记忆"能力,也解释了为什么必须保证 Hook 调用顺序的稳定性。