# 阅读图解React笔记8
Table of Contents
本文仅仅是阅读 图解 React 原理系列 的笔记,了解更多内容请查看原文链接。
Hook 原理
Hook 与 Fiber
Hook 最终也是为了控制 fiber 节点的状态和副作用,从 fiber 视角,状态和副作用的相关属性如下:
export type Fiber = { // 省略其他。。。
// fiber 节点自身状态相关 pendingProps: any, memoizedProps: any, updateQueue: any, memoizedState: any,
// fiber 节点副作用相关 flags: Flags, nextEffect: Fiber | null, firstEffect: Fiber | null, lastEffect: Fiber | null,};使用 Hook 的任意一个 API,最后都是为了控制上述这几个 fiber 属性。
Hook 数据结构
在 react-reconciler/src/ReactFiberHooks.new.js,定义了 Hook 的数据结构:
type Update<S, A> = { lane: Lane, action: A, eagerReducer: ((S, A) => S) | null, eagerState: S | null, next: Update<S, A> priority?: ReactPriorityLevel,}
type UpdateQueue<S, A> = { pending: Update<S, A> | null, dispatch: (A => mixed) | null, lastRenderedReducer: ((S, A) => S) | null, lastRenderedState: S | null,}
export type Hook = { memoizedState: any, baseState: any, baseQueue: Update<any, any> | null, queue: UpdateQueue<any, any> | null, next: Hook | null,}从定义上看,Hook 对象共有 5 个属性:
- hook.memoizedState: 保持在内存中的局部状态。
- hook.baseState: hook.baseQueue 中所有 update 对象合并之后的状态。
- hook.baseQueue: 存储 update 对象的环形链表,只包括高于本次渲染优先级的 update 对象。
- hook.queue: 存储 update 对象的环形链表,包括所有优先级的 update 对象。
- hook.next: next 指针,指向链表中的下一个 hook。
所以 Hook 是一个链表,单个 Hook 拥有自己的状态 hook.memoizedState 和 自己的更新队列 hook.queue。
注意:其中 hook.queue 与 fiber.updateQueue 虽然都是 update 环形链表,尽管 update 对象的数据结构与处理方式都是高度相似,但是这两个队列中的 update 对象是独立的。hook.queue 只作用于 hook 对象的状态维护。
Hook 分类
在 18.2.0 中,共定义了 17 种 Hook 类型:
export type HookType = | 'useState' | 'useReducer' | 'useContext' | 'useRef' | 'useEffect' | 'useInsertionEffect' | 'useLayoutEffect' | 'useCallback' | 'useMemo' | 'useImperativeHandle' | 'useDebugValue' | 'useDeferredValue' | 'useTransition' | 'useMutableSource' | 'useSyncExternalStore' | 'useId' | 'useCacheRefresh';官网上将其分为两个类别,状态 Hook(State Hook)和副作用 Hook(Effect Hook)。
调用函数前
在调用函数前,react 内部还需要做一些准备工作。
处理函数
从 fiber 树构造的视角来看,不同的 fiber 类型,只需要调用不同的处理函数返回 fiber 子节点。所以在 performUnitOfWork -> beginWork 函数中,调用了多种处理函数。
本节讨论 Hook,所以列出其中的 updateFunctionComponent 函数,其源码位置:ReactFiberBeginWork.new.js
// 只保留 FunctionComponent 相关的代码function beginWork(current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes): Fiber | null { const updateLanes = workInProgress.lanes; // 省略其他代码。。。 switch (workInProgress.tag) { // 省略其他代码。。。 case FunctionComponent: { const Component = workInProgress.type; const unresolvedProps = workInProgress.pendingProps; const resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent( current, workInProgress, Component, resolvedProps, renderLanes, ); } }}
function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) { // 省略其他代码
// 进入 Hook 相关代码,返回下级 ReactElement 对象 nextChildren = renderWithHooks( current, workInProgress, Component, nextProps, context, renderLanes, );
reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child;}在 updateFunctionComponent 函数中调用了 renderWithHooks,至此 Fiber 与 Hook 产生了关联。
renderWithHooks 函数
renderWithHooks 源码位于 ReactFiberHooks.new.js
export function renderWithHooks<Props, SecondArg>( current: Fiber | null, workInProgress: Fiber, Component: (p: Props, arg: SecondArg) => any, props: Props, secondArg: SecondArg, nextRenderLanes: Lanes,) { // 1. 设置全局变量 renderLanes = nextRenderLanes; currentlyRenderingFiber = workInProgress;
// 清除当前 fiber 的遗留状态 workInProgress.memoizedState = null; workInProgress.updateQueue = null; workInProgress.lanes = NoLanes;
// 2. 调用函数,生成子级 ReactElement 对象 // 指定 dispatcher, 区分 mount 和 update ReactCurrentDispatcher.current = current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; // 执行函数,其中进行分析 hooks 的使用 let children = Component(props, secondArg);
// 3. 重置全局变量,返回 children renderLanes = NoLanes; currentlyRenderingFiber = (null: any);
currentHook = null; workInProgressHook = null; didScheduleRenderPahseUpdate = false;
return children;}- 调用函数前:设置全局变量,标记渲染优先级和当前 fiber,清除当前 fiber 的遗留状态。
- 调用函数:构造出 hooks 链表,最后生成子级 ReactElement 对象(children)。
- 调用函数后:重置全局变量,返回 children。
调用函数
Hooks 执行流程
所有的 hook 执行流程大体一致:
-
FC 进入 render 流程前,确定 ReactCurrentDispatcher.current 指向。
-
进入 mount 流程时,执行 mount 对应逻辑,方法名一般为 mountXXX。
function mountXXX() {// 获取对应的 hookconst hook = mountWorkInProgressHook();// 省略,执行 hook 自身的操作}// 常见的 useState 对应的 mountfunction mountState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>] {const hook = mountWorkInProgressHook();// 省略其他代码。。。return [hook.memoizedState, dispatch];}// 常见的 useEffect 对应的 mountfunction mountEffectImpl(fiberFlags, hookFlag, create, deps): void {const hook = mountWorkInProgressHook();// 省略其他代码。。。} -
update 时,执行 update 对应逻辑,方法名一般为 updateXXX。
function updateXXX() {// 获取对应的 hookconst hook = updateWorkInProgressHook()// 省略,执行 hook 自身的操作} -
其他情况 hook 执行,依据 ReactCurrentDispatcher.current 指向做不同的处理。
链式存储
查看 ReactFiberHooks.new.js 文件中的 mountWorkInProgressHook
function mountWorkInProgressHook(): Hook { const hook: Hook = { memoizedState: null, baseState: null, baseQueue: null, queue: null, next: null, };
if (workInProgressHook === null) { // 链表中首个 hook currentlyRenderingFiber.memoizedState = workInProgress = hook; } else { // 将hook添加到链表末尾 workInProgressHook = workInProgressHook.next = hook; }
return workInProgressHook;}逻辑是创建 Hook 并挂载到 fiber.memoizedState 上,多个 Hook 以链表结构保存。
顺序克隆
fiber 树构造(对比更新)阶段,执行 updateFunctionComponent -> renderWithHooks 时,执行 useState 和 useEffect 在 fiber 对比更新分别对应 updateState -> updateReducer 和 updateEffect -> updateEffectImpl。
function updateWorkInProgressHook(): Hook { // 移动 currentHook 指针 let nextCurrentHook: null | Hook; if (currentHook === null) { const current = currentlyRenderingFiber.alternate; if (current !== null) { nextCurrentHook = current.memoizedState; } else { nextCurrentHook = null; } } else { nextCurrentHook = currentHook.next; }
// 移动 workInProgressHook 指针 let nextWorkInProgressHook: null | Hook; if (workInProgressHook === null) { nextWorkInProgressHook = currentlyRenderingFiber.memoizedState; } else { nextWorkInProgressHook = workInProgressHook.next; }
if (nextWorkInProgressHook !== null) { workInProgressHook = nextWorkInProgressHook; nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook; } else { currentHook = nextCurrentHook;
const newHook: Hook = { memoizedState: currentHook.memoizedState, baseState: currentHook.baseState, baseQueue: currentHook.baseQueue, next: null, };
if (workInProgressHook === null) { currentlyRenderingFiber.memoizedState = workInProgressHook = newHook; } else { workInProgressHook = workInProgressHook.next = newHook; } } return workInProgressHook;}updateWorkInProgressHook 函数的逻辑目的是为了让 currentHook 和 workInProgressHook 两个指针同时向后移动。