react hook实现原理

107 阅读3分钟

React Hook 实现原理

总体实现原理

React Hook 的实现依赖于 Fiber 架构。在 Fiber 中,每个组件实例都有一个对应的 Fiber 节点,Fiber 节点上有一个 memoizedState 属性,用于存储这个组件的状态。对于函数组件,Hook 的状态也是存储在 Fiber 节点的 memoizedState 上的。

Hook 在 Fiber 节点上是以链表的形式存储的。每个 Hook 对应链表中的一个节点,这些节点通过 next 指针连接。

一、数据结构

1. Fiber 节点

在 React Fiber 架构中,每个组件对应一个 Fiber 节点。Fiber 节点上有一个 memoizedState 属性,用于存储函数组件的 Hook 链表。

javascript

// 简化版的 Fiber 节点
{
  memoizedState: hookLinkedListHead, // Hook 链表的头节点
  stateNode: ...,
  // ... 其他属性
}
2. Hook 节点

每个 Hook 函数(如 useState、useEffect)都会在 Hook 链表中添加一个节点。Hook 节点的结构如下:

javascript

// 简化版的 Hook 节点
{
  memoizedState: any, // 用于存储 Hook 的状态(如 useState 的状态、useEffect 的依赖等)
  next: Hook | null,   // 指向下一个 Hook 节点
  // 其他特定于 Hook 的属性
}

不同类型的 Hook 使用 memoizedState 存储不同的内容:

  • useState: 存储 state 的值
  • useEffect: 存储一个 effect 对象,包含回调函数、依赖等
  • useMemo: 存储计算值和依赖
  • useCallback: 存储回调和依赖
3. Effect 对象

对于 useEffect,它会创建一个 effect 对象,结构如下:

javascript

{
  tag: EffectTag, // 标识 effect 的类型(如 Layout、Passive)
  create: () => (() => void) | void, // 回调函数
  destroy: (() => void) | void, // 清理函数
  deps: any[] | null, // 依赖数组
  next: Effect | null, // 指向下一个 effect
}

二、实现流程

1. 初始化阶段

当函数组件首次渲染时,React 会执行函数组件,同时会依次执行组件内部的所有 Hook。

  • React 会创建一个 Hook 链表,并将 Hook 节点挂载到 Fiber 节点的 memoizedState 上。
  • 每个 Hook 函数(如 useState)会读取对应 Hook 节点的 memoizedState 来获取初始值。
2. 更新阶段

当组件更新时,React 会再次执行函数组件。

  • React 会沿着 Hook 链表依次执行每个 Hook。
  • Hook 函数会读取当前 Hook 节点的状态,并返回更新后的值(对于 useState)或执行副作用(对于 useEffect)。
3. 副作用执行

对于 useEffect,React 会在渲染完成后(根据 effect 的 tag 在不同的阶段)执行副作用。具体来说:

  • 在渲染阶段,React 会记录下 effect,并在 commit 阶段执行。
  • 执行 effect 时,会先执行前一个 effect 的清理函数(如果存在),再执行当前 effect 的回调函数。
4. 依赖比较

对于有依赖数组的 Hook(如 useEffect、useMemo),React 会使用 Object.is 来比较依赖项是否发生变化。如果依赖项发生变化,则执行相应的操作(如执行 effect 或重新计算 memoized 值)。

5. 链表的重要性

Hook 链表保证了 Hook 的状态在每次渲染时都能被正确找到。这就是为什么 Hook 必须在组件的顶层调用,不能有条件地使用 Hook。因为 React 依赖于 Hook 的调用顺序来关联每次渲染的状态。