好的 👍,你问的是 React 16 中所有 Hooks 的底层实现和原理 。
这里我会分几个层次来解释:
- React 16 的背景
- Hooks 的设计理念
- 核心 Hooks 的底层实现思路 (
useState
,useEffect
,useContext
, ...) - React 内部的 Hook 数据结构
- 执行流程(函数组件渲染时 Hook 的调用顺序)
1. 背景:React 16 与 Hooks
- React 16.8 是第一个支持 Hooks 的版本。
- 目标:让函数组件具备状态(state)和副作用(effect)的能力,替代部分 class 组件逻辑(比如
componentDidMount
/componentDidUpdate
/componentWillUnmount
)。 - 核心思想 :通过"调用顺序"来匹配 Hook 与组件的 fiber 节点,避免显式 id。
2. Hooks 的设计理念
React Hooks 有几个原则:
- 只能在函数组件顶层调用,不能在条件语句或循环里动态调用。
-
- 这是因为 Hooks 依赖调用顺序来定位。
- 只能在 React 函数组件或自定义 Hook 中调用。
- 每个组件 fiber 节点维护一个单链表,链表节点就是 Hook 对象。
👉 这就是为什么 eslint-plugin-react-hooks
会强制规则:不要在 if/for 中调用 Hook。
3. 常见 Hooks 的底层原理
(1) useState
- 本质是
useReducer
的语法糖。 - 内部维护一个 Hook 节点对象(链表的一部分):
typescript
type Hook = {
memoizedState: any, // 当前的 state
baseState: any, // 初始 state
queue: UpdateQueue, // 更新队列(链表结构)
next: Hook | null // 指向下一个 Hook
}
- 当你调用
useState(initial)
时: -
- 创建一个更新对象(
action
)。 - 挂到
queue
上。 - 触发
scheduleUpdateOnFiber
,进入调度。
- 创建一个更新对象(
-
- React 在当前 fiber 上创建一个 Hook 节点,
memoizedState = initial
。 - 返回
[state, dispatch]
。 dispatch
本质是一个函数:
- React 在当前 fiber 上创建一个 Hook 节点,
- 渲染时,React 会执行
processUpdateQueue
,消费queue
,更新memoizedState
。
(2) useEffect
- 保存副作用函数和依赖数组。
- Hook 节点结构:
typescript
type Effect = {
tag: HookFlags, // effect 类型(Passive, Layout 等)
create: () => (() => void) | void, // 副作用函数
destroy?: () => void, // 清理函数
deps: any[] | null, // 依赖
next: Effect | null
}
- 调用
useEffect(fn, deps)
时: -
- 如果
deps
变了,就调用上一次的destroy
(清理),再执行新的create
。 - 如果没变,则跳过。
- 如果
-
- React 在 Hook 节点里记录
create
和deps
。 - 在 commit 阶段(DOM 更新后),React 比较新旧依赖:
- React 在 Hook 节点里记录
(3) useLayoutEffect
- 和
useEffect
类似,但执行时机不同: -
useLayoutEffect
在 DOM 更新后、浏览器绘制前 执行(同步)。useEffect
在 浏览器绘制后 执行(异步,延迟)。
- 用于测量 DOM、同步操作。
(4) useContext
- 获取 Context 的值。
- 机制:
-
- 组件调用
useContext(MyContext)
时,会在 fiber 上记录对MyContext
的依赖。 - 当 Context Provider 的值变化时,React 会找到所有依赖它的消费 fiber,触发更新。
- 组件调用
(5) useReducer
-
类似 Redux 的 reducer:
-
-
useState
底层其实就是useReducer
的特例:scssuseState(initial) === useReducer((s, a) => typeof a === 'function' ? a(s) : a, initial)
-
-
保存 reducer 和
state
,通过dispatch
触发更新。
(6) useRef
- 维护一个
{ current: any }
对象,整个生命周期内保持引用不变。 - 实现:只在 Hook 节点里存一个
memoizedState = { current: initialValue }
,之后不会更新。
(7) useMemo
/ useCallback
- 本质是"带缓存的值 / 函数"。
- Hook 节点保存
value
和deps
。 - 渲染时对比依赖数组:
-
- 如果没变,直接返回旧值。
- 如果变了,重新计算并缓存。
4. React 内部 Hook 数据结构
在 fiber 节点上,每个函数组件对应一个 Hook 链表:
typescript
type Fiber = {
memoizedState: Hook | null; // hook 链表头
updateQueue: any;
// ...
}
renderWithHooks
函数:-
- 进入函数组件渲染时,初始化
currentlyRenderingFiber
。 - 每调用一个 Hook,就在链表上走一步,复用或新建节点。
- 保证 "调用顺序一致 = Hook 顺序一致"。
- 进入函数组件渲染时,初始化
5. Hook 执行流程总结
- 初次渲染
-
- 执行函数组件,依次调用
useState
/useEffect
... - 为每个 Hook 创建一个 Hook 节点,挂到 fiber.memoizedState 链表上。
- 执行函数组件,依次调用
- 更新渲染
-
- 执行组件时,React 会遍历 fiber.memoizedState 链表。
- 每次调用 Hook,就拿到对应节点,复用之前的状态。
- 如果调用顺序错了(比如把 Hook 放到 if 里),链表错位,React 会报错。
- 提交阶段(commit phase)
-
- 对于
useEffect
/useLayoutEffect
,会根据依赖比较来执行副作用。 - 对于
useState
/useReducer
,会处理更新队列,得到新 state。
- 对于
✅ 总结:
- React Hooks 的底层实现是 fiber 节点上的 Hook 链表 + 调用顺序保证。
- 每个 Hook 维护一个小的状态对象,存储
memoizedState
、queue
、deps
等。 useState
基于更新队列;useEffect
延迟到 commit 阶段执行;useRef
只是一个稳定对象;useMemo
/useCallback
做依赖比较。