React Hooks 的工作原理
字数 2310 2025-11-02 17:10:18
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 也是以同样的方式,按照调用顺序被添加到链表中。
- React 会为这个组件的 Fiber 节点初始化一个
- 在组件后续的更新渲染(update)时:
- React 会再次执行这个函数组件。
- 当它再次遇到
useState时,它不会创建一个新的 Hook 对象。相反,它会顺着这个组件 Fiber 节点上的 Hook 链表,按顺序逐个取出之前对应的 Hook 对象,并返回其中存储的当前状态值。 - 这个过程完全依赖于调用顺序。如果第二次渲染时 Hook 的调用顺序和第一次不同,React 顺着链表去找,就会把第一个 Hook 的状态错误地关联到第二个 Hook 上,导致 bug。
- 当你在一个函数组件中调用多个 Hooks(例如,两个
-
为什么 Hook 调用顺序不能变(Hooks Rules)
- 基于上面的原理,就很容易理解 React 官方提出的规则:"只在最顶层使用 Hook" 和 "不要在循环、条件或嵌套函数中调用 Hook"。
- 举例说明:假设我们有如下代码:
function MyComponent({ isVisible }) { if (isVisible) { const [name, setName] = useState('Alice'); // 第一个Hook } const [age, setAge] = useState(30); // 第二个Hook }- 第一次渲染:
isVisible为true。- 执行顺序:Hook1 (
useState('Alice')) -> Hook2 (useState(30))。 - React 内部链表:
[Hook1(name), Hook2(age)]。
- 执行顺序:Hook1 (
- 第二次渲染:
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 调用顺序的稳定性。