React Hooks 内部实现原理与函数组件更新机制
React Hooks 内部实现原理与函数组件更新机制
Hooks 的内部实现原理
React Hooks 的实现依赖于以下几个关键机制:
1. 链表结构存储 Hook 状态
React 使用单向链表来管理 Hooks 的状态。每个 Hook 节点包含:
type Hook = {memoizedState: any, // 存储当前状态baseState: any, // 基础状态baseQueue: Update<any, any> | null, // 基础更新队列queue: UpdateQueue<any, any> | null, // 更新队列next: Hook | null, // 指向下一个 Hook
};
2. 当前 Hook 指针
React 内部维护一个 currentHook
指针,它会随着组件的渲染过程依次指向链表中的每个 Hook:
let currentlyRenderingFiber: Fiber | null = null;
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
3. Hooks 调用顺序的重要性
Hooks 必须无条件地在组件顶层调用,这是因为 React 依赖于调用顺序来正确关联 Hook 和它的状态:
function updateFunctionComponent(fiber) {// 重置指针currentlyRenderingFiber = fiber;fiber.memoizedHooks = null;currentHook = null;workInProgressHook = null;// 执行组件函数const children = Component(props);// 渲染完成后重置currentlyRenderingFiber = null;currentHook = null;workInProgressHook = null;return children;
}
函数组件更新机制
1. 调度阶段
当状态更新时,React 会:
- 创建一个更新对象并加入更新队列
- 调度一次新的渲染(通过
scheduleUpdateOnFiber
)
2. 渲染阶段
在渲染阶段,React 会:
- 调用函数组件
- 按顺序执行 Hooks
- 返回新的 React 元素
function renderWithHooks(current, workInProgress, Component, props) {// 设置当前正在渲染的 FibercurrentlyRenderingFiber = workInProgress;// 重置 Hook 链表workInProgress.memoizedState = null;// 执行组件函数const children = Component(props);// 重置状态currentlyRenderingFiber = null;return children;
}
3. 提交阶段
在提交阶段,React 会将渲染结果应用到 DOM 上,并执行副作用(useEffect 等)。
常见 Hook 的实现原理
useState
function useState(initialState) {return useReducer(basicStateReducer,initialState);
}function basicStateReducer(state, action) {return typeof action === 'function' ? action(state) : action;
}
useEffect
function useEffect(create, deps) {const fiber = currentlyRenderingFiber;const effect = {tag: HookEffectTag, // 标识是 effectcreate, // 副作用函数destroy: undefined, // 清理函数deps, // 依赖数组next: null, // 下一个 effect};// 将 effect 添加到 fiber 的 updateQueueif (fiber.updateQueue === null) {fiber.updateQueue = { lastEffect: null };}const lastEffect = fiber.updateQueue.lastEffect;if (lastEffect === null) {fiber.updateQueue.lastEffect = effect.next = effect;} else {const firstEffect = lastEffect.next;lastEffect.next = effect;effect.next = firstEffect;fiber.updateQueue.lastEffect = effect;}
}
useMemo
function useMemo(nextCreate, deps) {const hook = mountWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;const nextValue = nextCreate();hook.memoizedState = [nextValue, nextDeps];return nextValue;
}
关键点总结
-
Hooks 依赖于调用顺序:React 使用链表结构按顺序存储 Hook 状态,因此不能在条件或循环中使用 Hooks。
-
双缓存技术:React 使用 current 和 workInProgress 两棵树来实现异步可中断的渲染。
-
闭包陷阱:函数组件每次渲染都会创建新的闭包,这解释了为什么有时候会拿到旧的 state 或 props。
-
批量更新:React 会合并多个状态更新,避免不必要的重复渲染。
-
副作用调度:useEffect 的副作用会在浏览器完成布局与绘制之后延迟执行。
理解这些原理有助于编写更高效、更可靠的 React 应用,并能更好地调试 Hook 相关的问题。