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) {
// 设置当前正在渲染的 Fiber
currentlyRenderingFiber = 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, // 标识是 effect
create, // 副作用函数
destroy: undefined, // 清理函数
deps, // 依赖数组
next: null, // 下一个 effect
};
// 将 effect 添加到 fiber 的 updateQueue
if (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 相关的问题。