Skip to content

React Hooks 源码分析 ​

按源码调用链梳理函数组件挂载与更新时的 Hooks 执行机制:renderWithHooks 如何在两套调度器之间切换、Hook 节点如何串成链表、useState 的更新队列如何入队与回放、useEffect 的依赖对比与 tag 标记,以及 useMemo、useRef 的缓存实现。源码位置:react-reconciler/src/ReactFiberHooks.js(expirationTime 调度时期的实现)。

一、renderWithHooks ​

函数组件初始化(mount)和更新(update)时都会调用该函数。

js
const NoWork = 0;

export function renderWithHooks(
  current: Fiber | null, // 渲染树,在 mount 阶段值为空
  workInProgress: Fiber, // 内存中构建的树
  Component, // 函数组件本身
  props,
  secondArg,
  nextRenderExpirationTime: number,
) {
  renderExpirationTime = nextRenderExpirationTime;
  // 用于后续更新时判断传入的节点是否是当前正在渲染的节点
  // 浅拷贝,currentlyRenderingFiber 与 workInProgress 指向同一内存地址
  currentlyRenderingFiber = workInProgress;
  // 置空 workInProgress 上的 memoizedState 与 updateQueue,
  // 后续会把新的 hooks 信息挂载到这两个属性上
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.expirationTime = NoWork;

  // mount 阶段使用 HooksDispatcherOnMount
  // update 阶段使用 HooksDispatcherOnUpdate
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;

  // 执行函数组件
  let children = Component(props, secondArg);

  if (workInProgress.expirationTime === renderExpirationTime) {
    // 检查是否有渲染阶段更新,这里的逻辑先放一放
  }

  // 判断 hooks 是否在函数组件内部执行,否则捕获并抛出异常
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

  renderExpirationTime = NoWork;
  // 函数执行完毕,将 currentlyRenderingFiber 置空
  currentlyRenderingFiber = null;
  // 分别将 current 树和 workInProgress 树上指向当前 hooks 节点的字段置空
  currentHook = null;
  workInProgressHook = null;

  // 暂停更新标识
  didScheduleRenderPhaseUpdate = false;

  return children;
}

关键变量:

  • current:渲染树。
  • workInProgress:内存中构建的树。组件更新时会复制 current 树作为 workInProgress 树,在组件 commit 阶段再把 workInProgress 赋值给 current。
  • workInProgress.memoizedState:在类组件中存放 state 信息;在函数组件中以链表的形式存放 hooks 信息。
  • workInProgress.expirationTime:确定更新的优先级(number 类型)。
  • currentHook:指向 current 树当前调度的 hooks 节点。
  • workInProgressHook:指向 workInProgress 树当前调度的 hooks 节点。

二、ReactCurrentDispatcher ​

在 renderWithHooks 函数中,通过判断 current 的值来调用不同的调度器。

挂载阶段的调度器:

js
// 常用的 hooks
const HooksDispatcherOnMount = {
  useCallback: mountCallback,
  useEffect: mountEffect,
  useLayoutEffect: mountLayoutEffect,
  useMemo: mountMemo,
  useReducer: mountReducer,
  useRef: mountRef,
  useState: mountState,
};

更新阶段的调度器:

js
const HooksDispatcherOnUpdate = {
  useCallback: updateCallback,
  useEffect: updateEffect,
  useLayoutEffect: updateLayoutEffect,
  useMemo: updateMemo,
  useReducer: updateReducer,
  useRef: updateRef,
  useState: updateState,
};

对于第一次渲染组件和更新组件,react-hooks 采用了两套 API:挂载阶段采用 HooksDispatcherOnMount,更新阶段采用 HooksDispatcherOnUpdate。

三、mountWorkInProgressHook ​

组件初始化的时候,每一次 hooks 执行都会调用 mountWorkInProgressHook 函数。

源码位置:react-reconciler/src/ReactFiberHooks.js → mountWorkInProgressHook

js
function mountWorkInProgressHook() {
  const hook: Hook = {
    memoizedState: null,
    baseState: null, // 一次更新中,产生的最新 state 值
    baseQueue: null, // useState 和 useReducer 中保存最新的更新队列,Update 类型
    queue: null, // 保存待更新队列
    next: null, // 指向下一个 hook 对象
  };
  // 例子中的第一个 hooks -> useState(0) 走的就是这样
  if (workInProgressHook === null) {
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

memoizedState 在不同 Hook 中保存的内容不同:

  • useState:保存 state 信息
  • useEffect:保存 effect 对象
  • useMemo:保存缓存的值和 deps
  • useRef:保存 ref 对象

通过链表的形式将 hooks 串联起来:currentlyRenderingFiber.memoizedState 指向链表的头节点,workInProgressHook 指向链表当前调度的节点。

因为是浅拷贝,所以这里 currentlyRenderingFiber.memoizedState 等同于 workInProgress.memoizedState。

示意图占位:原笔记此处有两张图(「第一步」「最后一步」),对比 hook 逐个入链前后的链表状态。

四、mountState ​

js
function mountState(initialState) {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === "function") {
    // 如果 useState 第一个参数为函数,执行函数得到 state
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    pending: null, // 待更新的
    dispatch: null, // 负责更新函数
    lastRenderedReducer: basicStateReducer, // 用于得到最新的 state
    lastRenderedState: initialState, // 最后一次得到的 state
  });

  const dispatch = (queue.dispatch = dispatchAction.bind(
    // 负责更新的函数
    null,
    currentlyRenderingFiber,
    queue,
  ));
  return [hook.memoizedState, dispatch];
}

将初始化的 state 赋值给 mountWorkInProgressHook 产生的 hook 对象的 memoizedState 和 baseState 属性,然后创建一个 queue 对象,里面保存了负责更新的信息。

dispatchAction 的结构:

js
function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
)

以 const [number, setNumber] = useState(0) 为例,dispatchAction 就是 setNumber:它的第一个参数和第二个参数已经被 bind 改为 currentlyRenderingFiber 和 queue,我们传入的参数是第三个参数 action。

五、dispatchAction ​

1. 创建 update 对象 ​

js
function dispatchAction(fiber, queue, action) {
  // 第一部分
  // 创建一个 update 对象
  const update = {
    expirationTime,
    suspenseConfig,
    action,
    eagerReducer: null,
    eagerState: null,
    next: null,
  };
  /* 把创建的 update 加入更新队列 */
  const pending = queue.pending;
  if (pending === null) {
    // 如果更新队列为空,那么将当前更新作为更新队列的第一个节点
    // 并让它的 next 属性指向自身
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  // 让 queue.pending 指向最新的节点
  queue.pending = update;
}

假设有连续 3 个更新状态的操作:

js
setNumber("0");
setNumber("1");
setNumber("2");

示意图占位:原笔记此处有三张图(1. / 2. / 3.),分别对应三次 setNumber 之后的更新队列形态。

最终的 hooks 链表结构:在 Hook 链表上的 useState Hook 节点,通过循环链表的形式存放着历史更新操作,其中 queue.pending 一直指向最新一次的更新操作。

2. 判断是否更新是组件到达 update 阶段 ​

dispatchAction 将当前传入的值(action)和此时显示在屏幕上的值(currentState)进行浅比较,相等则退出,否则调用 scheduleWork 安排 fiberNode 的更新工作。

js
function dispatchAction(fiber, queue, action) {
  // 第二部分
  const alternate = fiber.alternate;
  /* 判断当前是否在渲染阶段 */
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    didScheduleRenderPhaseUpdate = true;
    update.expirationTime = renderExpirationTime;
    currentlyRenderingFiber.expirationTime = renderExpirationTime;
  } else {
    if (
      fiber.expirationTime === NoWork &&
      (alternate === null || alternate.expirationTime === NoWork)
    ) {
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        try {
          const currentState = queue.lastRenderedState; /* 上一次的 state */
          const eagerState = lastRenderedReducer(currentState, action);
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            return;
          }
        } catch (error) {
          // 抑制错误,render 阶段会再次抛出
        }
      }
    }
    scheduleWork(fiber, expirationTime);
  }
}

scheduleWork 并非马上让组件重新渲染,其中还有各种更新优先级的判断处理。例如这里的 useState,要等到当前所有的 setXxx() 都逐一执行完了,最终才会集中一次更新。

六、updateWorkInProgressHook ​

alternate:React 通过双缓冲树保存状态,一颗保存当前状态 current,一颗保存上一次状态 workInProgress,两颗 fiber 树用 alternate 相互指向。

updateWorkInProgressHook 的作用:

  1. 生成 workInProgressHook,获取正处于执行更新阶段的 hook
  2. 更新全局 currentHook
js
function updateWorkInProgressHook() {
  let nextCurrentHook;
  if (currentHook === null) {
    /* 如果 currentHook === null,证明它是第一个 hooks */
    // currentlyRenderingFiber 等于内存中构建的树 workInProgress
    // workInProgress.alternate 等于 current 树
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      // 将当前节点树的 hooks 信息存入 nextCurrentHook
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    /* 不是第一个 hooks,那么指向下一个 hooks */
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook;
  if (workInProgressHook === null) {
    // 第一次执行 hooks
    // 这里应该注意一下,当函数组件更新也是调用 renderWithHooks,
    // memoizedState 属性是置空的
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    /* 这个情况说明 renderWithHooks 执行过程发生多次函数组件的执行 */
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;
    currentHook = nextCurrentHook;
  } else {
    invariant(
      nextCurrentHook !== null,
      "Rendered more hooks than during the previous render.",
    );
    currentHook = nextCurrentHook;
    const newHook = {
      // 创建一个新的 hook
      memoizedState: currentHook.memoizedState,
      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,
      next: null,
    };
    if (workInProgressHook === null) {
      // 如果是第一个 hooks
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // 重新更新 hook
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}

逻辑:

  1. 首先如果是第一次执行 hooks 函数,那么从 current 树上取出 memoizedState。
  2. 然后声明变量 nextWorkInProgressHook。正常情况下一次 renderWithHooks 执行,workInProgress 的 memoizedState 会被置空,nextWorkInProgressHook 应该一直为 null。如果当前组件执行后还是处于渲染优先级,说明函数组件又有了新的更新任务,这就造成上述的不为 null 的情况。
  3. 最后复制 current 的 hooks,把它赋值给 workInProgressHook,用于更新新一轮的 hooks 状态。

七、updateState ​

组件在第一次渲染时使用 mountState,之后渲染执行 updateState:

js
function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

updateState 实际调用的是 updateReducer,这也证实了 useState 实际是简化版的 useReducer:调用 useState 的时候不会传入 reducer,因为这里会默认传一个 basicStateReducer 进去作为 reducer。

js
// 默认 reducer
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}

action 是函数则执行,否则直接返回 action。

js
function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // 获取正在执行的处于更新阶段 Hook 节点
  const hook = updateWorkInProgressHook();
  // 获取更新队列链表
  const queue = hook.queue;
  // ...
  // 获取更新队列最初的 update 对象节点
  // hook.baseQueue 存储最新的 update 节点,.next 则是最初的 update 节点
  let first = currentHook.baseQueue.next;
  // 初始化 newState
  let newState = currentHook.baseState;
  // ...
  let update = first;
  // ...
  do {
    // ...
    // 循环遍历更新队列链表
    // 从最早的那个 update 对象开始遍历,每次遍历执行一次更新,去更新状态
    const action = update.action;
    newState = reducer(newState, action);
    update = update.next;
  } while (update !== null && update !== first);
  // ...
  // 返回最新的状态和修改状态的方法
  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}

注意:上面的 ... 是原笔记省略的部分,不是可直接运行的完整代码。

执行步骤:

  1. 获取正在执行的处于更新阶段 Hook 节点。
  2. 获取该 hook 节点的更新队列链表。
  3. 从该更新队列最早的 update 对象节点开始遍历,一直遍历到最新添加的 update 对象节点,遍历到每个节点的时候执行该节点的更新操作,将该次更新的 state 值存到 newState 中。
  4. 当遍历完最近的一个 update 对象节点后,此时 newState 里存放的就是最新的 state 值,最后返回 newState,于是用户拿到最新的 state。

八、useState 运行流程 ​

1. 组件初次渲染时 ​

  1. 在 hooks 链表上添加该 useState 的 Hook 节点。
  2. 初始化 state 值。
  3. 返回此次渲染的 state 和修改 state 的方法。

2. 调用 setXxx / dispatchAction 时 ​

  1. 创建 update 对象,并将 update 对象添加到该 hook 节点的更新队列链表。
  2. 判断传入的值(action)和当前正在屏幕上渲染的 state 值是否相同(浅比较):相同则 return;不相同则调用 scheduleWork 安排组件的重新渲染。
  3. 当所有 setXxx 都逐一执行完后,假设能满足第 2 步的条件,则触发更新(组件重新渲染),进入 update 阶段。

3. 组件更新时 ​

  1. 获取该 useState Hook 的更新队列链表。
  2. 遍历这个更新队列链表,从最早的那个 update 对象开始遍历,直至遍历到最新添加的那一个 update 对象,最后得到最新的 state 并返回,作为此次组件渲染的 state。
  3. 返回此次渲染的 state 和修改 state 的方法。

九、mountEffect ​

js
function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  // ...
  return mountEffectImpl(
    UpdateEffect | PassiveEffect,
    HookPassive,
    create,
    deps,
  );
}

effect 的 tag:

  • HookHasEffect:表示该 effect 需要执行,如果没有这个 tag,那么这个 effect 将会被跳过,不会执行。
  • HookPassive:表示如果这个 effect 需要被执行,那么它将会在组件 UI 渲染完后执行(paint 之后)。
  • HookLayout:表示如果这个 effect 需要被执行,那么它将会在组件 UI 渲染之前执行(layout 之后,paint 之前)。

mountEffect 只是作为一个入口,真正开始干活的是 mountEffectImpl 函数:

js
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
  // 获取当前 Hook 节点,同时将当前 Hook 添加到 Hook 链表中
  const hook = mountWorkInProgressHook();

  // 获取依赖
  const nextDeps = deps === undefined ? null : deps;

  // 将当前 effect 对象保存到 hook.memoizedState 中
  // 以及添加到 fiberNode 的 updateQueue 上
  hook.memoizedState = pushEffect(
    HookHasEffect | hookEffectTag,
    create,
    undefined,
    nextDeps,
  );
}

pushEffect 的作用是将当前 effect 添加到 FiberNode 的 updateQueue.lastEffect 中:

js
function pushEffect(tag, create, destroy, deps) {
  const effect = {
    tag,
    create, // 副作用函数
    destroy, // 清除副作用函数
    deps, // 依赖项
    next, // 指向下一个 effect 对象
  };
  // 获取当前 FiberNode 的 updateQueue
  let componentUpdateQueue = currentlyRenderingFiber.updateQueue;

  if (componentUpdateQueue === null) {
    // 如果 updateQueue 为空,则创建一个新的 updateQueue
    componentUpdateQueue = createFunctionComponentUpdateQueue();
    currentlyRenderingFiber.updateQueue = componentUpdateQueue;
    // updateQueue.lastEffect 指向最新添加进来的 effect
    // 保持循环链表的结构
    componentUpdateQueue.lastEffect = effect.next = effect;
  } else {
    const lastEffect = componentUpdateQueue.lastEffect;
    if (lastEffect === null) {
      // 假如 lastEffect 指向 null,说明此时链表还不是循环链表的结构,
      // 那么就要控制最新的 effect 的 next 的指向,使其变为循环链表的结构
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      // 将当前 effect 添加到 updateQueue 中
      const firstEffect = lastEffect.next;
      lastEffect.next = effect;
      effect.next = firstEffect;
      // 令 lastEffect 始终指向最新添加进来的 effect
      componentUpdateQueue.lastEffect = effect;
    }
  }
  // 返回当前 effect
  return effect;
}

updateQueue.lastEffect 属性指向的始终是最新添加进队列的 effect,lastEffect.next 始终指向最早添加进来的 effect,以此形成一个循环链表的结构。(updateQueue 用于存储 effect 的循环链表,memoizedState 用于存储 hook 的单向链表。)

十、updateEffect ​

js
function updateEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  // ...
  return updateEffectImpl(
    UpdateEffect | PassiveEffect,
    HookPassive,
    create,
    deps,
  );
}

updateEffect 的调用流程和 mountEffect 相似,updateEffect 只是一个入口,真正干活的是 updateEffectImpl:

js
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
  // 获取当前 Hook 节点,并把它添加到 Hook 链表中
  const hook = updateWorkInProgressHook();

  // 获取依赖
  const nextDeps = deps === undefined ? null : deps;

  // 初始化清除 effect 函数
  let destroy = undefined;

  if (currentHook !== null) {
    // 获取上一次渲染的 Hook 节点的 effect
    const prevEffect = currentHook.memoizedState;
    // 获取上一次渲染的 Hook 节点的 effect 的清除函数
    destroy = prevEffect.destroy;
    if (nextDeps !== null) {
      // 获取上一次渲染的 Hook 节点的 effect 的依赖
      const prevDeps = prevEffect.deps;

      // 对比前后依赖的值是否相同
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 如果依赖的值相同,即依赖没有变化,
        // 那么只会给这个 effect 打上一个 HookPassive tag,
        // 然后在组件渲染完以后会跳过这个 effect 的执行
        pushEffect(hookEffectTag, create, destroy, nextDeps);
        return;
      }
    }
  }

  currentlyRenderingFiber.effectTag |= fiberEffectTag;

  // pushEffect 的作用是将当前 effect 添加到 FiberNode 的 updateQueue 中,
  // 然后把返回的当前 effect 保存到 Hook 节点的 memoizedState 属性中
  hook.memoizedState = pushEffect(
    HookHasEffect | hookEffectTag,
    create,
    destroy,
    nextDeps,
  );
}

update 阶段的 useEffect 和 mount 阶段所做的事情基本相似,唯独不一样的就是 update 阶段会考虑 effect 的依赖是否有变化:

  • 如果没有变化,那么只会给这次 effect 打上 HookPassive tag,在最后 commit 阶段(组件视图渲染完成后)会跳过 effect 的执行。
  • 如果依赖有变化,那么就会给这次 effect 打上 HookPassive 和 HookHasEffect 两个 tag,表示这个 effect 将会在组件视图渲染完成后执行。

小结:

  1. 获取当前 hook 节点,并把它添加到 hook 链表中。
  2. 获取本次 effect 的 deps 依赖。
  3. 拿本次 effect 的依赖和上一次渲染时的 effect 的依赖做对比:
    • 假如依赖没有发生改变,那么就只给这次 effect 打上 HookPassive 的 tag,在 commit 阶段(组件视图渲染完成后)跳过这一次 effect 的执行;
    • 假如依赖有发生改变,那么就会给这次 effect 打上 HookPassive 和 HookHasEffect 两个 tag,在 commit 阶段(组件视图渲染完成后)执行这一次 effect。
  4. 将本次 effect 添加到 fiberNode 的 updateQueue 中,并将本次 effect 保存在当前 Hook 节点的 memoizedState 中。

十一、useEffect 运行流程 ​

1. 组件初次渲染 ​

  1. 执行 useEffect 时,将 useEffect Hook 添加到 Hook 链表中,然后创建 fiberNode 的 updateQueue,并把本次 effect 添加到 updateQueue 中。
  2. 渲染组件的 UI。
  3. 完成 UI 渲染后,执行本次 effect。

2. 组件重新渲染 ​

  1. 执行 useEffect 时,将 useEffect Hook 添加到 Hook 链表中,判断依赖:
    • 假设没有传入依赖,那么直接给这个 effect 打上「需要执行」的 tag。
    • 假如有传入依赖 deps,并且当前依赖和上次渲染时的依赖对比有发生改变,那么就给这个 effect 打上「需要执行」的 tag。
    • 假如有传入依赖 deps,但是依赖没有发生改变,则不会给这个 effect 打上「需要执行」的 tag。
    • 假如有传入依赖 deps,但是传入的是一个空数组 [],那么也不会给这个 effect 打上「需要执行」的 tag。
  2. 渲染组件的 UI。
  3. 假如有清除函数(effect 中的 return 内容),则执行上一次渲染的清除函数;如果依赖是 [],则先不用执行清除函数,而是等到组件销毁时才执行。
  4. 判断本次 effect 是否有「需要执行」的 tag(HookHasEffect):如果有,就执行本次 effect;如果没有,就直接跳过,不执行本次 effect。

3. 组件销毁时 ​

  1. 在组件销毁之前,先执行完组件上次渲染时的清除函数。

十二、mountMemo ​

js
function mountMemo(nextCreate, deps) {
  const hook = mountWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps;
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

初始化 useMemo,就是创建一个 hook,然后执行 useMemo 的第一个参数,得到需要缓存的值,然后将值和 deps 记录下来,赋值给当前 hook 的 memoizedState。

十三、updateMemo ​

js
function updateMemo(nextCreate, deps) {
  const hook = updateWorkInProgressHook();
  const nextDeps = deps === undefined ? null : deps; // 新的 deps 值
  const prevState = hook.memoizedState;
  if (prevState !== null) {
    if (nextDeps !== null) {
      const prevDeps = prevState[1]; // 之前保存的 deps 值
      if (areHookInputsEqual(nextDeps, prevDeps)) {
        // 判断两次 deps 值
        return prevState[0];
      }
    }
  }
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, nextDeps];
  return nextValue;
}

在组件更新中,判断两次 deps 是否相等:如果不相等,证明依赖项发生改变,那么执行 useMemo 的第一个函数,得到新的值,然后重新赋值给 hook.memoizedState;如果相等,那么直接获取缓存的值。

十四、mountRef ​

js
function mountRef(initialValue) {
  const hook = mountWorkInProgressHook();
  const ref = { current: initialValue };
  hook.memoizedState = ref;
  return ref;
}

创建一个 ref 对象,对象的 current 属性用来保存初始化的值,最后用 hook.memoizedState 保存 ref。

十五、updateRef ​

js
function updateRef(initialValue) {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

返回缓存的值,也就是无论函数组件执行多少次,hook.memoizedState 内存中都指向同一个对象。

参考链接 ​