hook 原理

首先引入官网上对hook 的解释.

  1. hook只能用于function
  2. 可以使用state以及其他的React特性

演示代码:

  1. import React, { useState, useEffect } from 'react';
  2. function Example() {
  3. Promise.resolve().then(() => {
  4. console.log(
  5. '所有的effect.create都是通过调度器(scheduler)异步(MessageChannel)执行的, 故effect.create函数必然在此之后执行',
  6. );
  7. });
  8. // 第1个hook(useState)
  9. const [count, setCount] = useState(0);
  10. // 第2个hook(useEffect)
  11. useEffect(() => {
  12. console.log('第1个effect.create dps: []');
  13. return () => {
  14. console.log('第1个effect.destroy');
  15. };
  16. }, []);
  17. // 第3个hook(useEffect)
  18. useEffect(() => {
  19. console.log('effect.create dps: [count]', count);
  20. return () => {
  21. console.log('第2个effect.destroy dps: [count]', count);
  22. };
  23. }, [count]);
  24. return (
  25. <>
  26. <p>You clicked {count} times</p>
  27. <button onClick={() => setCount(count + 1)}>Click me</button>
  28. </>
  29. );
  30. }
  31. export default Example;

新增阶段

创建 fiber

通过fiber 构建(新增节点)中的介绍, 创建节点都是在beginWork阶段.

function类型节点在新增时调用mountIndeterminateComponent

  1. // 省略了与创建fiber无关的逻辑
  2. function mountIndeterminateComponent(
  3. _current,
  4. workInProgress,
  5. Component,
  6. renderExpirationTime,
  7. ) {
  8. const props = workInProgress.pendingProps;
  9. let value;
  10. value = renderWithHooks(
  11. null,
  12. workInProgress,
  13. Component,
  14. props,
  15. context,
  16. renderExpirationTime,
  17. );
  18. // React DevTools reads this flag.
  19. workInProgress.effectTag |= PerformedWork;
  20. // Proceed under the assumption that this is a function component
  21. workInProgress.tag = FunctionComponent;
  22. reconcileChildren(null, workInProgress, value, renderExpirationTime);
  23. return workInProgress.child;
  24. }

核心步骤:

  1. 调用renderWithHooks(目的和调用classInstance.render一样),返回代表子节点的reactElement
  2. 将步骤 1 中得到的reactElement传入reconcileChildren中去构建fiber次级子节点

逻辑进入ReactFiberHooks中, 这是一个独立的工作空间, 管理所有的hook对象.

  1. export function renderWithHooks<Props, SecondArg>(
  2. current: Fiber | null,
  3. workInProgress: Fiber,
  4. Component: (p: Props, arg: SecondArg) => any,
  5. props: Props,
  6. secondArg: SecondArg,
  7. nextRenderExpirationTime: ExpirationTime,
  8. ): any {
  9. renderExpirationTime = nextRenderExpirationTime;
  10. currentlyRenderingFiber = workInProgress;
  11. workInProgress.memoizedState = null;
  12. workInProgress.updateQueue = null;
  13. workInProgress.expirationTime = NoWork;
  14. // 1.设置全局HooksDispatcher
  15. ReactCurrentDispatcher.current =
  16. current === null || current.memoizedState === null
  17. ? HooksDispatcherOnMount
  18. : HooksDispatcherOnUpdate;
  19. // 2.执行`Component`
  20. let children = Component(props, secondArg);
  21. renderExpirationTime = NoWork;
  22. currentlyRenderingFiber = (null: any);
  23. currentHook = null;
  24. workInProgressHook = null;
  25. didScheduleRenderPhaseUpdate = false;
  26. return children;
  27. }

核心步骤:

  1. 设置当前hook操作代理ReactCurrentDispatcher
    • 新增节点, 设置为HooksDispatcherOnMount
    • 更新节点, 设置为HooksDispatcherOnUpdate
  2. 执行Component()

创建 hook

在执行Example()的时候, 调用useState(0)

跟进useState逻辑, 最终执行ReactCurrentDispatcher.current.useState(initialState).

新增节点, useState对应mountState

  1. function mountState<S>(
  2. initialState: (() => S) | S,
  3. ): [S, Dispatch<BasicStateAction<S>>] {
  4. //1. 创建hook对象, 并且挂载到当前`fiber.memoizedState`上,workInProgressHook指向当前hook
  5. const hook = mountWorkInProgressHook();
  6. if (typeof initialState === 'function') {
  7. // $FlowFixMe: Flow doesn't like mixed types
  8. initialState = initialState();
  9. }
  10. //2. 把initialState设置到`hook`对象中
  11. hook.memoizedState = hook.baseState = initialState;
  12. //3. 设置hook.queue
  13. // 设置lastRenderedReducer,lastRenderedState,dispatch
  14. const queue = (hook.queue = {
  15. pending: null,
  16. dispatch: null,
  17. lastRenderedReducer: basicStateReducer,
  18. lastRenderedState: (initialState: any),
  19. });
  20. // 设置queue.dispatch, 当前fiber被关联到queue.dispatch中
  21. const dispatch: Dispatch<
  22. BasicStateAction<S>,
  23. > = (queue.dispatch = (dispatchAction.bind(
  24. null,
  25. currentlyRenderingFiber,
  26. queue,
  27. ): any));
  28. //4. 返回dispatchAction操作接口
  29. return [hook.memoizedState, dispatch];
  30. }

核心步骤:

  1. 创建hook对象, 挂载到当前fiber.memoizedState,workInProgressHook指向此hook
  2. 设置hook.queue, 和当前fiber进行关联

新增节点, useEffect对应mountEffect

  1. function mountEffect(
  2. create: () => (() => void) | void,
  3. deps: Array<mixed> | void | null,
  4. ): void {
  5. // 注意这里指定了两种tag
  6. return mountEffectImpl(
  7. UpdateEffect | PassiveEffect, // fiber.effectTag 适用于fiber对象
  8. HookPassive, // effect.tag 适用于effect对象
  9. create,
  10. deps,
  11. );
  12. }
  13. function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
  14. const hook = mountWorkInProgressHook();
  15. const nextDeps = deps === undefined ? null : deps;
  16. currentlyRenderingFiber.effectTag |= fiberEffectTag;
  17. hook.memoizedState = pushEffect(
  18. HookHasEffect | hookEffectTag, //再次指定 effect.tag
  19. create,
  20. undefined,
  21. nextDeps,
  22. );
  23. }
  24. function pushEffect(tag, create, destroy, deps) {
  25. // 创建新的effect对象
  26. const effect: Effect = {
  27. tag,
  28. create,
  29. destroy,
  30. deps,
  31. // Circular
  32. next: (null: any),
  33. };
  34. // 将effect对象添加到fiber.updateQueue队列中
  35. let componentUpdateQueue: null | FunctionComponentUpdateQueue = (currentlyRenderingFiber.updateQueue: any);
  36. if (componentUpdateQueue === null) {
  37. componentUpdateQueue = createFunctionComponentUpdateQueue();
  38. currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);
  39. componentUpdateQueue.lastEffect = effect.next = effect;
  40. } else {
  41. const lastEffect = componentUpdateQueue.lastEffect;
  42. if (lastEffect === null) {
  43. componentUpdateQueue.lastEffect = effect.next = effect;
  44. } else {
  45. const firstEffect = lastEffect.next;
  46. lastEffect.next = effect;
  47. effect.next = firstEffect;
  48. componentUpdateQueue.lastEffect = effect;
  49. }
  50. }
  51. return effect;
  52. }

核心步骤:

  1. 创建hook对象, 并且添加到hook队列中,workInProgressHook指向此hook
  2. 设置fiber.effectTag = UpdateEffect | PassiveEffect
  3. 创建effect对象(设置effect.tag = HookHasEffect | HookPassive), 并将effect添加到fiber.UpdateQueue队列中

在整个创建hook阶段, 主要流程表示如下:

hook 原理 - 图1

执行 hook

从以上流程图可以看到, 执行function的时候, 只是创建了hook, 但是并没有执行hook.create.在 fiber 构建(新增节点) 中有介绍, commitRoot分为3 个阶段

第一个阶段commitBeforeMutationEffectsPassive类型的 tag 做了特殊处理.如果function类型的fiber使用了hookapi,会设置fiber.effectTag |= Passive

  1. function commitBeforeMutationEffects() {
  2. while (nextEffect !== null) {
  3. if (
  4. !shouldFireAfterActiveInstanceBlur &&
  5. focusedInstanceHandle !== null &&
  6. isFiberHiddenOrDeletedAndContains(nextEffect, focusedInstanceHandle)
  7. ) {
  8. shouldFireAfterActiveInstanceBlur = true;
  9. beforeActiveInstanceBlur();
  10. }
  11. const effectTag = nextEffect.effectTag;
  12. // `Passive`类型的tag做了特殊处理.
  13. if ((effectTag & Passive) !== NoEffect) {
  14. // If there are passive effects, schedule a callback to flush at
  15. // the earliest opportunity.
  16. if (!rootDoesHavePassiveEffects) {
  17. rootDoesHavePassiveEffects = true;
  18. // 需要注意, 此处的回调函数是通过调度器执行的, 所以是一个异步回调
  19. scheduleCallback(NormalPriority, () => {
  20. flushPassiveEffects(); // 执行hook的入口
  21. return null;
  22. });
  23. }
  24. }
  25. nextEffect = nextEffect.nextEffect;
  26. }
  27. }

异步回调:

flushPassiveEffectsImpl -> commitPassiveHookEffects

  1. export function commitPassiveHookEffects(finishedWork: Fiber): void {
  2. if ((finishedWork.effectTag & Passive) !== NoEffect) {
  3. switch (finishedWork.tag) {
  4. case FunctionComponent:
  5. case ForwardRef:
  6. case SimpleMemoComponent:
  7. case Block:
  8. commitHookEffectListUnmount(
  9. HookPassive | HookHasEffect,
  10. finishedWork,
  11. );
  12. commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
  13. break;
  14. }
  15. default:
  16. break;
  17. }
  18. }
  19. }
  20. // 执行destroy方法
  21. function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
  22. const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  23. const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  24. if (lastEffect !== null) {
  25. const firstEffect = lastEffect.next;
  26. let effect = firstEffect;
  27. do {
  28. if ((effect.tag & tag) === tag) {
  29. // Unmount
  30. const destroy = effect.destroy;
  31. effect.destroy = undefined;
  32. if (destroy !== undefined) {
  33. destroy();
  34. }
  35. }
  36. effect = effect.next;
  37. } while (effect !== firstEffect);
  38. }
  39. }
  40. // 执行create方法
  41. function commitHookEffectListMount(tag: number, finishedWork: Fiber) {
  42. const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  43. const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  44. if (lastEffect !== null) {
  45. const firstEffect = lastEffect.next;
  46. let effect = firstEffect;
  47. do {
  48. if ((effect.tag & tag) === tag) {
  49. // Mount
  50. const create = effect.create;
  51. effect.destroy = create();
  52. }
  53. effect = effect.next;
  54. } while (effect !== firstEffect);
  55. }
  56. }

更新阶段

dispatchAction 触发调度

  1. function dispatchAction<S, A>(
  2. fiber: Fiber,
  3. queue: UpdateQueue<S, A>,
  4. action: A,
  5. ) {
  6. // 1. 获取expirationTime
  7. const currentTime = requestCurrentTimeForUpdate();
  8. const suspenseConfig = requestCurrentSuspenseConfig();
  9. const expirationTime = computeExpirationForFiber(
  10. currentTime,
  11. fiber,
  12. suspenseConfig,
  13. );
  14. // 2. 创建update对象
  15. const update: Update<S, A> = {
  16. expirationTime,
  17. suspenseConfig,
  18. action,
  19. eagerReducer: null,
  20. eagerState: null,
  21. next: (null: any),
  22. };
  23. //3. 将update对象设置到hook.queue队列当中
  24. // Append the update to the end of the list.
  25. const pending = queue.pending;
  26. if (pending === null) {
  27. // This is the first update. Create a circular list.
  28. update.next = update;
  29. } else {
  30. update.next = pending.next;
  31. pending.next = update;
  32. }
  33. queue.pending = update;
  34. const alternate = fiber.alternate;
  35. //4. 请求调度
  36. scheduleUpdateOnFiber(fiber, expirationTime);
  37. }
  38. }

dispatchAction创建了一个新的update对象, 添加到hook.queue.pending队列之后.

hook 原理 - 图2

更新 fiber

scheduleUpdateOnFiber请求调度, 随后再次构建fiber树, renderWithHooks作为构建fiber树过程中的一环, 也会再次执行.

更新 hook

renderWithHooks调用栈中, 执行Example函数体, 调用useState,useEffect等函数重新构造hook对象.

currentHookworkInProgressHook是两个指针, 分别指向老hook和新hook

updateWorkInProgressHook维护了当前fiber节点新旧hook指针的移动, 保证currentHookworkInProgressHook有正确的指向

  1. function updateWorkInProgressHook(): Hook {
  2. let nextCurrentHook: null | Hook;
  3. // 刚进入该fiber节点, currentHook为null
  4. if (currentHook === null) {
  5. // 拿到当前fiber节点的副本(对应老节点)
  6. const current = currentlyRenderingFiber.alternate;
  7. if (current !== null) {
  8. nextCurrentHook = current.memoizedState;
  9. } else {
  10. nextCurrentHook = null;
  11. }
  12. } else {
  13. nextCurrentHook = currentHook.next;
  14. }
  15. let nextWorkInProgressHook: null | Hook;
  16. if (workInProgressHook === null) {
  17. nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  18. } else {
  19. nextWorkInProgressHook = workInProgressHook.next;
  20. }
  21. if (nextWorkInProgressHook !== null) {
  22. // There's already a work-in-progress. Reuse it.
  23. workInProgressHook = nextWorkInProgressHook;
  24. nextWorkInProgressHook = workInProgressHook.next;
  25. currentHook = nextCurrentHook;
  26. } else {
  27. // Clone from the current hook.
  28. currentHook = nextCurrentHook;
  29. // 创建新的hook节点, 最后添加到队列
  30. const newHook: Hook = {
  31. memoizedState: currentHook.memoizedState,
  32. baseState: currentHook.baseState,
  33. baseQueue: currentHook.baseQueue,
  34. queue: currentHook.queue,
  35. next: null,
  36. };
  37. if (workInProgressHook === null) {
  38. // This is the first hook in the list.
  39. currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
  40. } else {
  41. // Append to the end of the list.
  42. workInProgressHook = workInProgressHook.next = newHook;
  43. }
  44. }
  45. return workInProgressHook;
  46. }

对于useState, 调用updateReducer:

  1. 调用updateWorkInProgressHook创建新的hook对象
    • 注意新hook实际上是旧hook的浅拷贝

hook 原理 - 图3

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

hook 原理 - 图4

最后返回新的元组[hook.memoizedState, dispatch]

  1. function updateReducer<S, I, A>(
  2. reducer: (S, A) => S,
  3. initialArg: I,
  4. init?: I => S,
  5. ): [S, Dispatch<A>] {
  6. const hook = updateWorkInProgressHook();
  7. const queue = hook.queue;
  8. queue.lastRenderedReducer = reducer;
  9. const current: Hook = (currentHook: any);
  10. // The last rebase update that is NOT part of the base state.
  11. let baseQueue = current.baseQueue;
  12. // The last pending update that hasn't been processed yet.
  13. const pendingQueue = queue.pending;
  14. if (pendingQueue !== null) {
  15. // We have new updates that haven't been processed yet.
  16. // We'll add them to the base queue.
  17. if (baseQueue !== null) {
  18. // Merge the pending queue and the base queue.
  19. const baseFirst = baseQueue.next;
  20. const pendingFirst = pendingQueue.next;
  21. baseQueue.next = pendingFirst;
  22. pendingQueue.next = baseFirst;
  23. }
  24. current.baseQueue = baseQueue = pendingQueue;
  25. queue.pending = null;
  26. }
  27. if (baseQueue !== null) {
  28. // We have a queue to process.
  29. const first = baseQueue.next;
  30. let newState = current.baseState;
  31. let newBaseState = null;
  32. let newBaseQueueFirst = null;
  33. let newBaseQueueLast = null;
  34. let update = first;
  35. do {
  36. const updateExpirationTime = update.expirationTime;
  37. if (updateExpirationTime < renderExpirationTime) {
  38. // Priority is insufficient. Skip this update. If this is the first
  39. // skipped update, the previous update/state is the new base
  40. // update/state.
  41. const clone: Update<S, A> = {
  42. expirationTime: update.expirationTime,
  43. suspenseConfig: update.suspenseConfig,
  44. action: update.action,
  45. eagerReducer: update.eagerReducer,
  46. eagerState: update.eagerState,
  47. next: (null: any),
  48. };
  49. if (newBaseQueueLast === null) {
  50. newBaseQueueFirst = newBaseQueueLast = clone;
  51. newBaseState = newState;
  52. } else {
  53. newBaseQueueLast = newBaseQueueLast.next = clone;
  54. }
  55. // Update the remaining priority in the queue.
  56. if (updateExpirationTime > currentlyRenderingFiber.expirationTime) {
  57. currentlyRenderingFiber.expirationTime = updateExpirationTime;
  58. markUnprocessedUpdateTime(updateExpirationTime);
  59. }
  60. } else {
  61. // This update does have sufficient priority.
  62. if (newBaseQueueLast !== null) {
  63. const clone: Update<S, A> = {
  64. expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
  65. suspenseConfig: update.suspenseConfig,
  66. action: update.action,
  67. eagerReducer: update.eagerReducer,
  68. eagerState: update.eagerState,
  69. next: (null: any),
  70. };
  71. newBaseQueueLast = newBaseQueueLast.next = clone;
  72. }
  73. // Mark the event time of this update as relevant to this render pass.
  74. // TODO: This should ideally use the true event time of this update rather than
  75. // its priority which is a derived and not reversible value.
  76. // TODO: We should skip this update if it was already committed but currently
  77. // we have no way of detecting the difference between a committed and suspended
  78. // update here.
  79. markRenderEventTimeAndConfig(
  80. updateExpirationTime,
  81. update.suspenseConfig,
  82. );
  83. // Process this update.
  84. if (update.eagerReducer === reducer) {
  85. // If this update was processed eagerly, and its reducer matches the
  86. // current reducer, we can use the eagerly computed state.
  87. newState = ((update.eagerState: any): S);
  88. } else {
  89. const action = update.action;
  90. newState = reducer(newState, action);
  91. }
  92. }
  93. update = update.next;
  94. } while (update !== null && update !== first);
  95. if (newBaseQueueLast === null) {
  96. newBaseState = newState;
  97. } else {
  98. newBaseQueueLast.next = (newBaseQueueFirst: any);
  99. }
  100. // Mark that the fiber performed work, but only if the new state is
  101. // different from the current state.
  102. if (!is(newState, hook.memoizedState)) {
  103. markWorkInProgressReceivedUpdate();
  104. }
  105. hook.memoizedState = newState;
  106. hook.baseState = newBaseState;
  107. hook.baseQueue = newBaseQueueLast;
  108. queue.lastRenderedState = newState;
  109. }
  110. const dispatch: Dispatch<A> = (queue.dispatch: any);
  111. return [hook.memoizedState, dispatch];
  112. }

对于useEffect, 调用updateEffectImpl:

  1. 调用updateWorkInProgressHook创建新的hook对象
    • useState中是一致的(不同的是, 通过useEffect创建的hook对象,hook.queue = null)

hook 原理 - 图5

  1. 生成新的effect对象

    • hook 更新,并且 deps 依赖不变.

      • 生成新的effect(HookPassive),添加到fiber.updateQueue

      hook 原理 - 图6

    • deps 依赖改变.

      • 设置fiber.effectTag = UpdateEffect | PassiveEffect
      • 生成新的effect(HookHasEffect | HookPassive),添加到fiber.updateQueue

      hook 原理 - 图7

  1. function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void {
  2. const hook = updateWorkInProgressHook();
  3. const nextDeps = deps === undefined ? null : deps;
  4. let destroy = undefined;
  5. if (currentHook !== null) {
  6. // hook更新
  7. const prevEffect = currentHook.memoizedState;
  8. destroy = prevEffect.destroy;
  9. if (nextDeps !== null) {
  10. const prevDeps = prevEffect.deps;
  11. if (areHookInputsEqual(nextDeps, prevDeps)) {
  12. // deps依赖一致
  13. pushEffect(hookEffectTag, create, destroy, nextDeps);
  14. return;
  15. }
  16. }
  17. }
  18. // 新增hook, 或者deps依赖改变
  19. // 1. 设置fiber.effectTag = UpdateEffect | PassiveEffect
  20. currentlyRenderingFiber.effectTag |= fiberEffectTag;
  21. // 2. 设置hook.memoizedState
  22. hook.memoizedState = pushEffect(
  23. HookHasEffect | hookEffectTag,
  24. create,
  25. destroy,
  26. nextDeps,
  27. );
  28. }

整个hook的更新过程可以如下表示:

左边是current右边是workInProgress

hook 原理 - 图8

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