Hook 原理
React Hook 原理
React Hook 允许我们在函数组件中使用 state 和其他 React 特性,其核心原理主要依赖以下几点:
1. 闭包 (Closures)
- 每个 Hook (如
useState
,useEffect
) 在组件的每次渲染中被调用时,都会形成一个闭包。 useState
返回的state
变量和setState
函数,以及useEffect
的回调函数,它们都能够“记住”它们被创建时的特定组件实例和特定的状态槽位。setState
函数的身份通常是稳定的,但它内部知道要更新哪个组件的哪个状态。
2. 内部数据结构 (Hooks 链表/数组)
- React 在内部为每个函数组件实例维护一个 Hooks 列表(通常被描述为链表或数组)。
- 当你调用
useState
,useEffect
等 Hook 时,React 会按照它们被调用的顺序将 Hook 的信息(如 state 值、依赖项、effect 函数等)存储在这个列表中。 - 关键点: 这就是为什么 Hooks 必须在组件的顶层调用,并且不能在循环、条件或嵌套函数中调用。React 依赖于每次渲染时 Hooks 的调用顺序完全一致,才能正确地从这个内部列表中获取和更新对应 Hook 的状态。
- 第一次渲染时,
useState()
调用将状态存储在列表的第一个位置,第二次useState()
调用存储在第二个位置,以此类推。 - 后续渲染时,React 再次按顺序调用这些 Hooks,并从对应位置取出之前存储的状态或信息。
- 第一次渲染时,
3. Fiber 架构
- React 的 Fiber 架构是实现 Hooks 的基础。Fiber 是 React 核心算法的重写,它使得渲染工作可以被中断、恢复以及分配优先级。
- 每个组件实例在内存中都有一个对应的 Fiber 节点。
- Hook 的状态和相关信息就存储在这些 Fiber 节点的
memoizedState
(或其他类似) 属性上,这个属性通常指向前面提到的 Hooks 链表的头部。 - 当
setState
被调用时:- React 会找到对应的 Fiber 节点。
- 标记该 Fiber 节点需要更新。
- 调度一次新的渲染。
- 在重新渲染该组件时,当再次执行到
useState
,React 会从 Fiber 节点上读取当前 Hook 对应的最新状态值返回。
4. useState
的简要工作流程
- 首次渲染:
- 调用
useState(initialState)
。 - React 创建一个新的 state 槽位(在组件的 Fiber 节点的 Hooks 链表中)。
- 存储
initialState
。 - 返回
[initialState, dispatchFunction]
(即setState
)。
- 调用
- 后续渲染:
- 调用
useState()
(此时不再需要initialState
参数,React 会忽略它)。 - React 根据 Hook 的调用顺序,从 Fiber 节点的 Hooks 链表中找到对应的 state 槽位。
- 返回
[currentState, dispatchFunction]
。
- 调用
- 调用
setState(newState)
或setState(updaterFn)
:setState
函数被调用时,它不会立即修改 state。- 它会创建一个更新对象,并将其调度到对应 Fiber 节点的更新队列中。
- React 调度器会在未来的某个时间点处理这个更新,触发组件的重新渲染。
- 如果使用的是函数式更新
setState(updaterFn)
,React 在执行更新时,会将当前最新的 state 作为参数传递给updaterFn
,并将返回值作为新的 state。
5. useEffect
的简要工作流程
- 渲染完成后:
- 组件完成渲染并提交到 DOM 后,React 会执行
useEffect
的回调函数。 - 如果是首次执行,或者依赖项数组中的值与上一次渲染时相比发生了变化,回调函数会被执行。
- 如果回调函数返回了一个清理函数,React 会在下一次该 effect 即将重新执行之前,或者在组件卸载时,执行这个清理函数。
- React 会保存当前的依赖项数组,用于下一次比较。
- 组件完成渲染并提交到 DOM 后,React 会执行
总结来说,Hooks 通过闭包捕获信息,依赖于严格的调用顺序和 Fiber 节点上的内部存储来实现在函数组件中管理状态和副作用的能力。