hook 原理
首先引入官网上对hook 的解释.
hook只能用于function中- 可以使用
state以及其他的React特性
演示代码:
import React, { useState, useEffect } from 'react';function Example() {Promise.resolve().then(() => {console.log('所有的effect.create都是通过调度器(scheduler)异步(MessageChannel)执行的, 故effect.create函数必然在此之后执行',);});// 第1个hook(useState)const [count, setCount] = useState(0);// 第2个hook(useEffect)useEffect(() => {console.log('第1个effect.create dps: []');return () => {console.log('第1个effect.destroy');};}, []);// 第3个hook(useEffect)useEffect(() => {console.log('effect.create dps: [count]', count);return () => {console.log('第2个effect.destroy dps: [count]', count);};}, [count]);return (<><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></>);}export default Example;
新增阶段
创建 fiber
通过fiber 构建(新增节点)中的介绍, 创建节点都是在beginWork阶段.
function类型节点在新增时调用mountIndeterminateComponent
// 省略了与创建fiber无关的逻辑function mountIndeterminateComponent(_current,workInProgress,Component,renderExpirationTime,) {const props = workInProgress.pendingProps;let value;value = renderWithHooks(null,workInProgress,Component,props,context,renderExpirationTime,);// React DevTools reads this flag.workInProgress.effectTag |= PerformedWork;// Proceed under the assumption that this is a function componentworkInProgress.tag = FunctionComponent;reconcileChildren(null, workInProgress, value, renderExpirationTime);return workInProgress.child;}
核心步骤:
- 调用
renderWithHooks(目的和调用classInstance.render一样),返回代表子节点的reactElement - 将步骤 1 中得到的
reactElement传入reconcileChildren中去构建fiber次级子节点
逻辑进入ReactFiberHooks中, 这是一个独立的工作空间, 管理所有的hook对象.
export function renderWithHooks<Props, SecondArg>(current: Fiber | null,workInProgress: Fiber,Component: (p: Props, arg: SecondArg) => any,props: Props,secondArg: SecondArg,nextRenderExpirationTime: ExpirationTime,): any {renderExpirationTime = nextRenderExpirationTime;currentlyRenderingFiber = workInProgress;workInProgress.memoizedState = null;workInProgress.updateQueue = null;workInProgress.expirationTime = NoWork;// 1.设置全局HooksDispatcherReactCurrentDispatcher.current =current === null || current.memoizedState === null? HooksDispatcherOnMount: HooksDispatcherOnUpdate;// 2.执行`Component`let children = Component(props, secondArg);renderExpirationTime = NoWork;currentlyRenderingFiber = (null: any);currentHook = null;workInProgressHook = null;didScheduleRenderPhaseUpdate = false;return children;}
核心步骤:
- 设置当前
hook操作代理ReactCurrentDispatcher- 新增节点, 设置为
HooksDispatcherOnMount - 更新节点, 设置为
HooksDispatcherOnUpdate
- 新增节点, 设置为
- 执行
Component()
创建 hook
在执行Example()的时候, 调用useState(0)
跟进useState逻辑, 最终执行ReactCurrentDispatcher.current.useState(initialState).
新增节点, useState对应mountState
function mountState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {//1. 创建hook对象, 并且挂载到当前`fiber.memoizedState`上,workInProgressHook指向当前hookconst hook = mountWorkInProgressHook();if (typeof initialState === 'function') {// $FlowFixMe: Flow doesn't like mixed typesinitialState = initialState();}//2. 把initialState设置到`hook`对象中hook.memoizedState = hook.baseState = initialState;//3. 设置hook.queue// 设置lastRenderedReducer,lastRenderedState,dispatchconst queue = (hook.queue = {pending: null,dispatch: null,lastRenderedReducer: basicStateReducer,lastRenderedState: (initialState: any),});// 设置queue.dispatch, 当前fiber被关联到queue.dispatch中const dispatch: Dispatch<BasicStateAction<S>,> = (queue.dispatch = (dispatchAction.bind(null,currentlyRenderingFiber,queue,): any));//4. 返回dispatchAction操作接口return [hook.memoizedState, dispatch];}
核心步骤:
- 创建
hook对象, 挂载到当前fiber.memoizedState,workInProgressHook指向此hook - 设置
hook.queue, 和当前fiber进行关联
新增节点, useEffect对应mountEffect
function mountEffect(create: () => (() => void) | void,deps: Array<mixed> | void | null,): void {// 注意这里指定了两种tagreturn mountEffectImpl(UpdateEffect | PassiveEffect, // fiber.effectTag 适用于fiber对象HookPassive, // effect.tag 适用于effect对象create,deps,);}function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {const hook = mountWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;currentlyRenderingFiber.effectTag |= fiberEffectTag;hook.memoizedState = pushEffect(HookHasEffect | hookEffectTag, //再次指定 effect.tagcreate,undefined,nextDeps,);}function pushEffect(tag, create, destroy, deps) {// 创建新的effect对象const effect: Effect = {tag,create,destroy,deps,// Circularnext: (null: any),};// 将effect对象添加到fiber.updateQueue队列中let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);if (componentUpdateQueue === null) {componentUpdateQueue = createFunctionComponentUpdateQueue();currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);componentUpdateQueue.lastEffect = effect.next = effect;} else {const lastEffect = componentUpdateQueue.lastEffect;if (lastEffect === null) {componentUpdateQueue.lastEffect = effect.next = effect;} else {const firstEffect = lastEffect.next;lastEffect.next = effect;effect.next = firstEffect;componentUpdateQueue.lastEffect = effect;}}return effect;}
核心步骤:
- 创建
hook对象, 并且添加到hook队列中,workInProgressHook指向此hook - 设置
fiber.effectTag = UpdateEffect | PassiveEffect - 创建
effect对象(设置effect.tag = HookHasEffect | HookPassive), 并将effect添加到fiber.UpdateQueue队列中
在整个创建hook阶段, 主要流程表示如下:

执行 hook
从以上流程图可以看到, 执行function的时候, 只是创建了hook, 但是并没有执行hook.create.在 fiber 构建(新增节点) 中有介绍, commitRoot分为3 个阶段
第一个阶段commitBeforeMutationEffects对Passive类型的 tag 做了特殊处理.如果function类型的fiber使用了hookapi,会设置fiber.effectTag |= Passive
function commitBeforeMutationEffects() {while (nextEffect !== null) {if (!shouldFireAfterActiveInstanceBlur &&focusedInstanceHandle !== null &&isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle)) {shouldFireAfterActiveInstanceBlur = true;beforeActiveInstanceBlur();}const effectTag = nextEffect.effectTag;// `Passive`类型的tag做了特殊处理.if ((effectTag & Passive) !== NoEffect) {// If there are passive effects, schedule a callback to flush at// the earliest opportunity.if (!rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = true;// 需要注意, 此处的回调函数是通过调度器执行的, 所以是一个异步回调scheduleCallback(NormalPriority, () => {flushPassiveEffects(); // 执行hook的入口return null;});}}nextEffect = nextEffect.nextEffect;}}
异步回调:
flushPassiveEffectsImpl -> commitPassiveHookEffects
export function commitPassiveHookEffects(finishedWork: Fiber): void {if ((finishedWork.effectTag & Passive) !== NoEffect) {switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case SimpleMemoComponent:case Block:commitHookEffectListUnmount(HookPassive | HookHasEffect,finishedWork,);commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);break;}default:break;}}}// 执行destroy方法function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;if (lastEffect !== null) {const firstEffect = lastEffect.next;let effect = firstEffect;do {if ((effect.tag & tag) === tag) {// Unmountconst destroy = effect.destroy;effect.destroy = undefined;if (destroy !== undefined) {destroy();}}effect = effect.next;} while (effect !== firstEffect);}}// 执行create方法function commitHookEffectListMount(tag: number, finishedWork: Fiber) {const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;if (lastEffect !== null) {const firstEffect = lastEffect.next;let effect = firstEffect;do {if ((effect.tag & tag) === tag) {// Mountconst create = effect.create;effect.destroy = create();}effect = effect.next;} while (effect !== firstEffect);}}
更新阶段
dispatchAction 触发调度
function dispatchAction<S, A>(fiber: Fiber,queue: UpdateQueue<S, A>,action: A,) {// 1. 获取expirationTimeconst currentTime = requestCurrentTimeForUpdate();const suspenseConfig = requestCurrentSuspenseConfig();const expirationTime = computeExpirationForFiber(currentTime,fiber,suspenseConfig,);// 2. 创建update对象const update: Update<S, A> = {expirationTime,suspenseConfig,action,eagerReducer: null,eagerState: null,next: (null: any),};//3. 将update对象设置到hook.queue队列当中// Append the update to the end of the list.const pending = queue.pending;if (pending === null) {// This is the first update. Create a circular list.update.next = update;} else {update.next = pending.next;pending.next = update;}queue.pending = update;const alternate = fiber.alternate;//4. 请求调度scheduleUpdateOnFiber(fiber, expirationTime);}}
dispatchAction创建了一个新的update对象, 添加到hook.queue.pending队列之后.

更新 fiber
scheduleUpdateOnFiber请求调度, 随后再次构建fiber树, renderWithHooks作为构建fiber树过程中的一环, 也会再次执行.
更新 hook
renderWithHooks调用栈中, 执行Example函数体, 调用useState,useEffect等函数重新构造hook对象.
currentHook和workInProgressHook是两个指针, 分别指向老hook和新hook
updateWorkInProgressHook维护了当前fiber节点新旧hook指针的移动, 保证currentHook和workInProgressHook有正确的指向
function updateWorkInProgressHook(): Hook {let nextCurrentHook: null | Hook;// 刚进入该fiber节点, currentHook为nullif (currentHook === null) {// 拿到当前fiber节点的副本(对应老节点)const current = currentlyRenderingFiber.alternate;if (current !== null) {nextCurrentHook = current.memoizedState;} else {nextCurrentHook = null;}} else {nextCurrentHook = currentHook.next;}let nextWorkInProgressHook: null | Hook;if (workInProgressHook === null) {nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;} else {nextWorkInProgressHook = workInProgressHook.next;}if (nextWorkInProgressHook !== null) {// There's already a work-in-progress. Reuse it.workInProgressHook = nextWorkInProgressHook;nextWorkInProgressHook = workInProgressHook.next;currentHook = nextCurrentHook;} else {// Clone from the current hook.currentHook = nextCurrentHook;// 创建新的hook节点, 最后添加到队列const newHook: Hook = {memoizedState: currentHook.memoizedState,baseState: currentHook.baseState,baseQueue: currentHook.baseQueue,queue: currentHook.queue,next: null,};if (workInProgressHook === null) {// This is the first hook in the list.currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;} else {// Append to the end of the list.workInProgressHook = workInProgressHook.next = newHook;}}return workInProgressHook;}
对于useState, 调用updateReducer:
- 调用
updateWorkInProgressHook创建新的hook对象- 注意新
hook实际上是旧hook的浅拷贝
- 注意新

- 如果
hook.queue.pending !=== null- 遍历
hook.queue.pending队列, 提取足够优先级的update对象, 生成newState - 更新
hook和queue对象的相关属性
- 遍历

最后返回新的元组[hook.memoizedState, dispatch]
function updateReducer<S, I, A>(reducer: (S, A) => S,initialArg: I,init?: I => S,): [S, Dispatch<A>] {const hook = updateWorkInProgressHook();const queue = hook.queue;queue.lastRenderedReducer = reducer;const current: Hook = (currentHook: any);// The last rebase update that is NOT part of the base state.let baseQueue = current.baseQueue;// The last pending update that hasn't been processed yet.const pendingQueue = queue.pending;if (pendingQueue !== null) {// We have new updates that haven't been processed yet.// We'll add them to the base queue.if (baseQueue !== null) {// Merge the pending queue and the base queue.const baseFirst = baseQueue.next;const pendingFirst = pendingQueue.next;baseQueue.next = pendingFirst;pendingQueue.next = baseFirst;}current.baseQueue = baseQueue = pendingQueue;queue.pending = null;}if (baseQueue !== null) {// We have a queue to process.const first = baseQueue.next;let newState = current.baseState;let newBaseState = null;let newBaseQueueFirst = null;let newBaseQueueLast = null;let update = first;do {const updateExpirationTime = update.expirationTime;if (updateExpirationTime < renderExpirationTime) {// Priority is insufficient. Skip this update. If this is the first// skipped update, the previous update/state is the new base// update/state.const clone: Update<S, A> = {expirationTime: update.expirationTime,suspenseConfig: update.suspenseConfig,action: update.action,eagerReducer: update.eagerReducer,eagerState: update.eagerState,next: (null: any),};if (newBaseQueueLast === null) {newBaseQueueFirst = newBaseQueueLast = clone;newBaseState = newState;} else {newBaseQueueLast = newBaseQueueLast.next = clone;}// Update the remaining priority in the queue.if (updateExpirationTime > currentlyRenderingFiber.expirationTime) {currentlyRenderingFiber.expirationTime = updateExpirationTime;markUnprocessedUpdateTime(updateExpirationTime);}} else {// This update does have sufficient priority.if (newBaseQueueLast !== null) {const clone: Update<S, A> = {expirationTime: Sync, // This update is going to be committed so we never want uncommit it.suspenseConfig: update.suspenseConfig,action: update.action,eagerReducer: update.eagerReducer,eagerState: update.eagerState,next: (null: any),};newBaseQueueLast = newBaseQueueLast.next = clone;}// Mark the event time of this update as relevant to this render pass.// TODO: This should ideally use the true event time of this update rather than// its priority which is a derived and not reversible value.// TODO: We should skip this update if it was already committed but currently// we have no way of detecting the difference between a committed and suspended// update here.markRenderEventTimeAndConfig(updateExpirationTime,update.suspenseConfig,);// Process this update.if (update.eagerReducer === reducer) {// If this update was processed eagerly, and its reducer matches the// current reducer, we can use the eagerly computed state.newState = ((update.eagerState: any): S);} else {const action = update.action;newState = reducer(newState, action);}}update = update.next;} while (update !== null && update !== first);if (newBaseQueueLast === null) {newBaseState = newState;} else {newBaseQueueLast.next = (newBaseQueueFirst: any);}// Mark that the fiber performed work, but only if the new state is// different from the current state.if (!is(newState, hook.memoizedState)) {markWorkInProgressReceivedUpdate();}hook.memoizedState = newState;hook.baseState = newBaseState;hook.baseQueue = newBaseQueueLast;queue.lastRenderedState = newState;}const dispatch: Dispatch<A> = (queue.dispatch: any);return [hook.memoizedState, dispatch];}
对于useEffect, 调用updateEffectImpl:
- 调用
updateWorkInProgressHook创建新的hook对象- 和
useState中是一致的(不同的是, 通过useEffect创建的hook对象,hook.queue = null)
- 和

生成新的
effect对象hook 更新,并且 deps 依赖不变.
- 生成新的
effect(HookPassive),添加到fiber.updateQueue

- 生成新的
deps 依赖改变.
- 设置
fiber.effectTag = UpdateEffect | PassiveEffect - 生成新的
effect(HookHasEffect | HookPassive),添加到fiber.updateQueue

- 设置
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {const hook = updateWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;let destroy = undefined;if (currentHook !== null) {// hook更新const prevEffect = currentHook.memoizedState;destroy = prevEffect.destroy;if (nextDeps !== null) {const prevDeps = prevEffect.deps;if (areHookInputsEqual(nextDeps, prevDeps)) {// deps依赖一致pushEffect(hookEffectTag, create, destroy, nextDeps);return;}}}// 新增hook, 或者deps依赖改变// 1. 设置fiber.effectTag = UpdateEffect | PassiveEffectcurrentlyRenderingFiber.effectTag |= fiberEffectTag;// 2. 设置hook.memoizedStatehook.memoizedState = pushEffect(HookHasEffect | hookEffectTag,create,destroy,nextDeps,);}
整个hook的更新过程可以如下表示:
左边是current右边是workInProgress

注意浅蓝色背景的updateQueue队列中, 新effect会引用旧effect对象的destroy方法.
