React Hooks 源码解读之 useState

1,094 阅读10分钟

react 版本:v17.0.3

1、前言

React Hooks 源码解读之Hook入口 一文中,我们介绍了 Hooks 的入口及hook处理函数的挂载,从 hook 处理函数的挂载关系我们可以得到这样的等式:

  • 组件挂载阶段:

    useState = ReactCurrentDispatcher.current.useState = HooksDispatcherOnMount.useState = mountState;

  • 组件更新阶段:

    useState = ReactCurrentDispatcher.current.useState = HooksDispatcherOnUpdate.useState = updateState

因此,组件在挂载阶段,执行 useState,实际上执行的是 mountState,而在更新阶段,实际上执行的是 updateState 。

2、类型定义

我们先来看看 ReactFiberHooks.new.js 中几个类型的定义。

2.1 Hook

我们在函数组件里定义了多个 hook,那么函数组件是如何找到对应的 hook 信息呢?我们来看看 hook 的类型定义:

export type Hook = {|
  memoizedState: any, // 指向当前渲染节点 Fiber, 上一次完整更新之后的最终状态值
  baseState: any,     // 初始化 initialState, 已经每次 dispatch 之后 newState
  baseQueue: Update<any, any> | null,  // 当前需要更新的 Update ,每次更新完之后,会赋值上一个 update,方便 react 在渲染错误的边缘,数据回溯
  queue: UpdateQueue<any, any> | null, // 缓存的更新队列,存储多次更新行为
  next: Hook | null,  // 指向下一次useState对应的Hook对象,通过 next 串联每一 hooks
|};

函数组件的 hook 信息存储在对应 Fiber 节点的 memoizedState 属性上,代表函数组件里的第一个 hook,hooks 的数据结构为单向链表,每一个节点可以通过 next 指针找到下一个 hook 。

2.1 Update & UpdateQueue

每个 hook 的更新队列都会存储在 queue 属性里,执行更新队列里的更新任务后,就可以得到最新的 state 值,一个更新任务及更新队列的类型定义如下:

type Update<S, A> = {|
  lane: Lane,
  action: A,
  eagerReducer: ((S, A) => S) | null,
  eagerState: S | null,
  next: Update<S, A>,
|};

export type UpdateQueue<S, A> = {|
  pending: Update<S, A> | null,
  interleaved: Update<S, A> | null,
  lanes: Lanes,
  dispatch: (A => mixed) | null,
  lastRenderedReducer: ((S, A) => S) | null,
  lastRenderedState: S | null,
|};

Update 称作一个更新,在调度一次 React 更新时会用到。UpdateQueue 是 Update 的队列,同时还带有更新时的 dispatch。

3、挂载阶段

在挂载阶段,执行 useState,实际上执行的是 mountState,我们来看看这个函数的实现。

3.1 mountState

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  // 创建一个新的 hook 对象,并返回当前的 workInProgressHook
  const hook = mountWorkInProgressHook();
  // 如果 useState 的参数是 function,就执行该 function
    
  if (typeof initialState === 'function') {
    // $FlowFixMe: Flow doesn't like mixed types
    initialState = initialState();
  }
    
  // memoizedState 是用来记录当前useState应该返回的结果的
  // baseState 初始化 initialState
  // 将 initialState 分别赋值给 hook 对象的 memoizedState 属性 和 baseState 属性
  hook.memoizedState = hook.baseState = initialState;
    
  // 新建一个队列
  const queue = (hook.queue = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,  // useState使用基础reducer
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    // 绑定当前的 fiber 和 queue
    currentlyRenderingFiber, // currentlyRenderingFiber 全局变量,在 renderWithHooks() 中初始化为 workInProgress
    queue,
  ): any));
    // 返回一个 state 以及 更新 state 的函数
  return [hook.memoizedState, dispatch];
}

在组件首次加载时,执行 mountWorkInProgressHook 方法创建了一个新的 hook 对象,将初始的 state 赋值给了 hook 对象的 memoizedState 属性 和 baseState 属性,然后新建了一个队列,接着创建一个 dispatch (更新 state 的函数),将当前的 fiber 和 queue 绑定到 dispatch 上,最后返回一个 state 以及 更新 state 的函数。

3.2 mountWorkInProgressHook

在 mountState 中,使用 mountWorkInProgressHook() 函数创建了一个新的 hook 对象,我们来看看它是如何创建的:

// packages/react-reconciler/src/ReactFiberHooks.new.js

// 创建一个新的 hook 对象,并返回当前的 workInProgressHook 对象
// workInProgressHook 对象是全局对象,在 mountWorkInProgressHook 中首次初始化
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };
  
   // Hooks are stored as a linked list on the fiber's memoizedState field
  // 将 新建的 hook 对象以链表的形式存储在当前的 fiber 节点memoizedState属性上

  // 只有在第一次打开页面的时候,workInProgressHook 为空
  if (workInProgressHook === null) {
    // This is the first hook in the list
    // 链表上的第一个 hook
    
    // currentlyRenderingFiber: The work-in-progress fiber. I've named it differently to distinguish it fromthe work-in-progress hook.
    
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    // 已经存在 workInProgressHook 对象,则将新创建的这个 Hook 接在 workInProgressHook 的尾部,形成链表
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}

可以看到,在新建一个 hook 对象时,如果全局的 workInProgressHook 对象不存在 (值为 null),就将新建的 hook 对象赋值给 workInProgressHook 对象,同时将 hook 对象赋值给 currentlyRenderingFiber 的 memoizedState 属性,如果 workInProgressHook 对象已经存在,则将 hook 对象接在 workInProgressHook 的尾部,从而形成一个链表。

3.3 dispatchAction

在 mountState 中,通过 dispatchAction 创建了一个 dispatch 触发器,即更新 state 的函数,在创建 dispatch 触发器的时候,绑定了当前的 fiber 节点和新建的 queue 队列,我们来重点看看这个 dispatchAction:

function dispatchAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  
  const eventTime = requestEventTime();
  const lane = requestUpdateLane(fiber);

  // 创建一个新的update
  // action就是我们setCount里面的值(count+1, count+2, count+3…)
  const update: Update<S, A> = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: (null: any),
  };

  // 备用的 fiber
  const alternate = fiber.alternate;

  /**
   * 情况一: render 阶段的处理
   * if分支 if (fiber === currentlyRenderingFiber ||  (alternate !== null && alternate === currentlyRenderingFiber)
   * currentlyRenderingFiber 即 workInProgress,workInProgress存在代表当前处于render阶段
   * 在 render 阶段,做了以下处理:
   * 1、标记变量 didScheduleRenderPhaseUpdate,后续单独处理
   * 2、新建或更新环形链表的 updateQueue
   */

  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    
    // 涉及 Fiber 的调度

    // fiber === currentlyRenderingFiber 时是 re-render,即当前更新周期中又产生了新的周期
    // 如果是 re-render ,didScheduleRenderPhaseUpdateDuringThisPass 置为 ture,
    // 就会循环计数 numberOfReRenders 来记录 re-render 的次数
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;

    /**
     * 在 render 阶段对 updateQueue 的处理
     */
    const pending = queue.pending; // pending 是 updateQueue  中的一个 update
    if (pending === null) {
      // This is the first update. Create a circular list.
      // 这是首次更新,创建循环链表
      // 只有一个 update,自己指向自己,形成环形链表
      update.next = update;
    } else {
      // 更新循环链表
      update.next = pending.next;
      // 形成环形链表
      pending.next = update;
    }
    // 将环形链表的一个 更新重新赋值给 queue.pending
    queue.pending = update;


  } else {


    /**
     * re-render 阶段的更新 做处理
     */


    // 判断是否是re-render 阶段的更新
    if (isInterleavedUpdate(fiber, lane)) {
      // 优先级决定的更新
      const interleaved = queue.interleaved;
      if (interleaved === null) {
        // This is the first update. Create a circular list.
        // 新建环形链表
        update.next = update;
        // At the end of the current render, this queue's interleaved updates will
        // be transfered to the pending queue.
        // 在当前渲染结束时,此队列的交错更新将传输到挂起队列
        pushInterleavedQueue(queue);
      } else {
        // 更新 环形链表
        update.next = interleaved.next;
        interleaved.next = update;
      }
      // 更新队列
      queue.interleaved = update;


    } else {


      /**
       * 在更新阶段根据优先级对 update 进行处理
       */


      // 创建/更新存储update的环形链表
      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;
    }


    // fiber.lanes保存fiber上存在的update的优先级
    // fiber.lanes === NoLanes意味着fiber上不存在update
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {

      // 计算进入渲染阶段之前的下一个阶段的 state,如果新 state 与 当前状态相同,则退出渲染

      // The queue is currently empty, which means we can eagerly compute the
      // next state before entering the render phase. If the new state is the
      // same as the current state, we may be able to bail out entirely.
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        let prevDispatcher;
       
        try {
          const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
          // Stash the eagerly computed state, and the reducer used to compute
          // it, on the update object. If the reducer hasn't changed by the
          // time we enter the render phase, then the eager state can be used
          // without calling the reducer again.
          update.eagerReducer = lastRenderedReducer;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            // Fast path. We can bail out without scheduling React to re-render.
            // It's still possible that we'll need to rebase this update later,
            // if the component re-renders for a different reason and by that
            // time the reducer has changed.
            return;
          }
        } catch (error) {
          // Suppress the error. It will throw again in the render phase.
        } finally {
          if (__DEV__) {
            ReactCurrentDispatcher.current = prevDispatcher;
          }
        }
      }
    }


    // 获取当前 fiber,根据优先级区分同步任务和异步任务,同步任务应立即同步执行,最先渲染出来,异步任务走scheduler
    // https://juejin.cn/post/6898635086657224717#heading-16
    // scheduleUpdateOnFiber 去调度执行更新任务
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);

    if (isTransitionLane(lane) && root !== null) {
      let queueLanes = queue.lanes;

      // If any entangled lanes are no longer pending on the root, then they
      // must have finished. We can remove them from the shared queue, which
      // represents a superset of the actually pending lanes. In some cases we
      // may entangle more than we need to, but that's OK. In fact it's worse if
      // we *don't* entangle when we should.
      queueLanes = intersectLanes(queueLanes, root.pendingLanes);

      // Entangle the new transition lane with the other transition lanes.
      const newQueueLanes = mergeLanes(queueLanes, lane);
      queue.lanes = newQueueLanes;
      // Even if queue.lanes already include lane, we don't know for certain if
      // the lane finished since the last time we entangled it. So we need to
      // entangle it again, just to be sure.
      markRootEntangled(root, newQueueLanes);
    }
  }

  // 给当前的 update 做标记,在 performance 上可以看到改 update 的信息
  if (enableSchedulingProfiler) {
    markStateUpdateScheduled(fiber, lane);
  }
}

在 dispatchAction 中维护了一份 queue 的数据结构:

/**
 * 在 render 阶段对 updateQueue 的处理
 */
const pending = queue.pending; // pending 是 updateQueue  中的一个 update
if (pending === null) {
  // This is the first update. Create a circular list.
  // 这是首次更新,创建循环链表
  // 只有一个 update,自己指向自己,形成环形链表
  update.next = update;
} else {
  // 更新循环链表
  update.next = pending.next;
  // 形成环形链表
  pending.next = update;
}
// 将环形链表的一个 更新重新赋值给 queue.pending
queue.pending = update;

queue 是一个环形链表,其规则为:

  • queue.pending 指向最近一次更新
  • pending.next指向第一次更新

if 的第一个分支 fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber) 是对 Fiber 调度的处理,fiber === currentlyRenderingFiber 时是 re-render,即当前更新周期中又产生了新的周期,如果是 re-render,didScheduleRenderPhaseUpdateDuringThisPass 置为 true,而在 renderWithHooks 中,如果 didScheduleRenderPhaseUpdateDuringThisPass 为 true,就会循环计数 numberOfReRenders 来记录 re-render 的次数。

  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    // 这是一个render阶段触发的更新,需要标记变量didScheduleRenderPhaseUpdate,后续单独处理
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  }

如果是re-render 阶段的更新,当 fiber 上不存在优先级的 update 时,就去计算进入渲染阶段之前的下一个阶段的 state,如果计算出来的状态(eagerState) 与当前的状态(currentState) 相同,则会退出渲染。

// fiber.lanes保存fiber上存在的update的优先级
// fiber.lanes === NoLanes意味着fiber上不存在update
if (
  fiber.lanes === NoLanes &&
  (alternate === null || alternate.lanes === NoLanes)
) {

  // 计算进入渲染阶段之前的下一个阶段的 state,如果新 state 与 当前状态相同,则退出渲染

  // The queue is currently empty, which means we can eagerly compute the
  // next state before entering the render phase. If the new state is the
  // same as the current state, we may be able to bail out entirely.
  const lastRenderedReducer = queue.lastRenderedReducer;
  if (lastRenderedReducer !== null) {
    let prevDispatcher;
   
    try {
      const currentState: S = (queue.lastRenderedState: any);
      const eagerState = lastRenderedReducer(currentState, action);
      // Stash the eagerly computed state, and the reducer used to compute
      // it, on the update object. If the reducer hasn't changed by the
      // time we enter the render phase, then the eager state can be used
      // without calling the reducer again.
      update.eagerReducer = lastRenderedReducer;
      update.eagerState = eagerState;
      if (is(eagerState, currentState)) {
        // Fast path. We can bail out without scheduling React to re-render.
        // It's still possible that we'll need to rebase this update later,
        // if the component re-renders for a different reason and by that
        // time the reducer has changed.
        return;
      }
    }
  }
}

我们整理一下挂载阶段时的一个流程:

4、更新阶段

接下来我们来看看更新过程中 useState 实际调用的方法 updateState。

4.1 updateState

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

可以看到,其实 updateState 调用的是 updateReducer 。对于 useState 触发的 update action 来说,如果 action 是函数,就返回函数执行的结果,否则就直接返回 action 的值:

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  // $FlowFixMe: Flow doesn't like mixed types
  return typeof action === 'function' ? action(state) : action;
}

因此,useState 只是 useReducer 的一个特殊情况,其传入的 reducer 为 basicStateReducer,只负责改变 state,而非 useReducer 那样可以传入自定义的 reducer 。

接下来,我们看看 updateReducer 做了什么事情。

4.2 updateReducer

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // 获取当前正在工作中的 Hook,即 workInProgressHook
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  invariant(
    queue !== null,
    'Should have a queue. This is likely a bug in React. Please file an issue.',
  );

  queue.lastRenderedReducer = reducer;
  // currentHook 全局变量,当前 fiber 上的 hook 列表
  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;
  }

  // 情形二:
  // 在处理「更新」的过程中没有产生新的 update,根据优先级来处理 更新队列(base queue)上的 update
  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;

    // 根据优先级处理环形链表  base queue 上的 update
    do {
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {


        /**
         * 对低优先级的 update 进行处理:
         * 1、当前的 update 将会被跳过
         * 2、将当前的 update 拷贝一份,添加到更新队列的尾部
         */


        // 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> = {
          lane: updateLane,
          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.
        // TODO: Don't need to accumulate this. Instead, we can remove
        // renderLanes from the original lanes.
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {


        /**
         * 对高优先级的 update 进行处理:
         * 1、赋值一份当前的 update 进行备份
         * 2、调用 reducer 计算出新的  state
         */

        // This update does have sufficient priority.

        if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            // This update is going to be committed so we never want uncommit
            // it. Using NoLane works because 0 is a subset of all bitmasks, so
            // this will never be skipped by the check above.
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }

        // 使用 reducer 计算新的  state
        // 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 = 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();
    }
    // 更新队列上的update 处理完后,在 hook 对象行挂载新的 state 和 update queue
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }

  // 情形三
  // 对于 Interleaved 类型的 update,在渲染过程中不做处理
  // Interleaved updates are stored on a separate queue. We aren't going to
  // process them during this render, but we do need to track which lanes
  // are remaining.
  const lastInterleaved = queue.interleaved;
  if (lastInterleaved !== null) {
    let interleaved = lastInterleaved;
    do {
      const interleavedLane = interleaved.lane;
      currentlyRenderingFiber.lanes = mergeLanes(
        currentlyRenderingFiber.lanes,
        interleavedLane,
      );
      markSkippedUpdateLanes(interleavedLane);
      interleaved = ((interleaved: any).next: Update < S, A >);
    } while (interleaved !== lastInterleaved);
  } else if (baseQueue === null) {
    // `queue.lanes` is used for entangling transitions. We can set it back to
    // zero once the queue is empty.
    queue.lanes = NoLanes;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  // 返回新的 state 和  dispatch 函数  //useReducer 的使用 const [state, dispatch] = useReducer(reducer, initialArg, init);
  return [hook.memoizedState, dispatch];
}

updateReducer 分为三种情形:

  1. 产生了一个新的更新(update),将它添加到 更新队列(base queue) 中。

  2. 在处理「更新」的过程中没有产生新的 update,根据优先级来处理 更新队列(base queue)上的 update。

  3. 对于 interleaved 类型的 update,则不做处理,仅仅标记跟踪其优先级。

我们重点来关注下 updateReducer 的第二种情形:

  // 情形二:
  // 在处理「更新」的过程中没有产生新的 update,根据优先级来处理 更新队列(base queue)上的 update
  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;

    // 根据优先级处理环形链表  base queue 上的 update
    do {
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {


        /**
         * 对低优先级的 update 进行处理:
         * 1、当前的 update 将会被跳过
         * 2、将当前的 update 拷贝一份,添加到更新队列的尾部
         */


        // 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> = {
          lane: updateLane,
          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.
        // TODO: Don't need to accumulate this. Instead, we can remove
        // renderLanes from the original lanes.
        currentlyRenderingFiber.lanes = mergeLanes(
          currentlyRenderingFiber.lanes,
          updateLane,
        );
        markSkippedUpdateLanes(updateLane);
      } else {


        /**
         * 对高优先级的 update 进行处理:
         * 1、赋值一份当前的 update 进行备份
         * 2、调用 reducer 计算出新的  state
         */

        // This update does have sufficient priority.

        if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            // This update is going to be committed so we never want uncommit
            // it. Using NoLane works because 0 is a subset of all bitmasks, so
            // this will never be skipped by the check above.
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          newBaseQueueLast = newBaseQueueLast.next = clone;
        }

        // 使用 reducer 计算新的  state
        // 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 = 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();
    }
    // 更新队列上的update 处理完后,在 hook 对象行挂载新的 state 和 update queue
    hook.memoizedState = newState;
    hook.baseState = newBaseState;
    hook.baseQueue = newBaseQueueLast;

    queue.lastRenderedState = newState;
  }

对更新队列上的 update 进行处理时,会根据优先级的高低分别做不同的处理。

  • 对于低优先级的 update:

  • 低优先级的update拷贝一份,将其添加到更新队列尾部

  • 不会计算新的state

    if (!isSubsetOfLanes(renderLanes, updateLane)) {

    /**

    • 对低优先级的 update 进行处理:

    • 1、当前的 update 将会被跳过

    • 2、将当前的 update 拷贝一份,保存到新的更新队列上 */

    // 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> = { lane: updateLane, 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. // TODO: Don't need to accumulate this. Instead, we can remove // renderLanes from the original lanes. currentlyRenderingFiber.lanes = mergeLanes( currentlyRenderingFiber.lanes, updateLane, ); markSkippedUpdateLanes(updateLane); }

  • 对于高优先级的 update

  • 拷贝当前的update,将其添加到更新队列的尾部

  • 调用 reducer 计算新的 state

    else {

    /**

    • 对高优先级的 update 进行处理:
    • 1、赋值一份当前的 update 进行备份
    • 2、调用 reducer 计算出新的 state */

    // This update does have sufficient priority.

    if (newBaseQueueLast !== null) { const clone: Update<S, A> = { // This update is going to be committed so we never want uncommit // it. Using NoLane works because 0 is a subset of all bitmasks, so // this will never be skipped by the check above. lane: NoLane, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; newBaseQueueLast = newBaseQueueLast.next = clone; }

    // 使用 reducer 计算新的 state // 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); } }

4.3 updateWorkInProgressHook

在 updateReducer()函数中,通过 updateWorkInProgressHook() 获取到了当前正在工作中的 Hook,即 workInProgressHook,我们来看看 updateWorkInProgressHook 的实现:

function updateWorkInProgressHook(): Hook {
  // This function is used both for updates and for re-renders triggered by a
  // render phase update. It assumes there is either a current hook we can
  // clone, or a work-in-progress hook from a previous render pass that we can
  // use as a base. When we reach the end of the base list, we must switch to
  // the dispatcher used for mounts.

  // 获取 当前 hook 的下一个 hook
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate;
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }

  // 取下一个 hook 为当前的hook
  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.

    // 拷贝当前的 hook,作为当前正在工作中的 workInProgressHook

    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) {
      // 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;
}

这里分两种情况:

  • 如果是在 render 阶段,则会取下一个 hook 作为当前的hook,并返回 workInProgressHook;
  • 如果是在 re-render 阶段,则在当前处理周期中,继续取当前的 workInProgressHook 做更新处理,最后再返回 workInProgressHook。

对于 useState 的解析到这就结束了,最后我们总结一下 useState 的执行流程:

5、总结

函数组件通过 renderWithHooks 函数可以确定当前的 workInProgress fiber 节点,通过是否存在 current fiber 节点来判断当前是在 Mount 阶段还是 Update 阶段,并获取相应阶段的 ReactCurrentDispatcher,执行函数组件自己来获取自身的 children 。

在执行过程中,会执行对应阶段的 hook 函数,函数组件的 hooks 是单向链表结构,存储在 Fiber 节点的 memoizedState 属性上,通过 next 指针(hook.next) 依序获取下一个 hook 对象。在hook 对象的 queue 属性上,存储着 hook 的更新队列,它是环形单向链表,queue.pending 指向最新的 update,queue.pending.next 则指向的是第一个 update 。

通过执行 mountWorkInProgressHook 来创建一个新的 hook 对象,然后返回初始的 state 和触发 action 的 dispatch,通过执行 updateState (实际上执行的是updateReducer) 来处理 queue 中的 upate 以获取最新的state。

调用 dispatch 触发 action,发起更新任务调度,同时在 dispatchAction 里计算最新的state,并更新queue环形链表,然后执行 scheduleUpdateOnFiber,进入调度,再次进入到renderWithHooks,执行updateState (实际上执行的是updateReducer) ,得到新的state值返回,并重新计算渲染。