本文仅仅是阅读 图解 React 原理系列 的笔记,了解更多内容请查看原文链接。

状态 Hook

原理概念

前面介绍了 Hook 原理(概览),主要内容有:

  1. function 类型的 fiber 节点,它的处理函数是 updateFunctionComponent,其中再通过 renderWithHooks 调用 function。
  2. 在 function 中,通过 Hook Api 创建 Hook 对象。
    • 状态 Hook 实现了持久化
    • 副作用 Hook 则实现了维护 fiber.flags,并提供了副作用回调
  3. 多个 Hook 对象构成了一个链表结构,并挂载到 fiber.memoizedState 之上。
  4. fiber 树更新阶段,把 current.memoizedState 链表上的所有 Hook 按照顺序克隆到 workInProgress.memoizedState 上,实现数据的持久化。

创建 Hook

本节将深入分析状态 Hook 的特性和实现原理。

在 fiber 初次构造阶段,useState 对应的源码 mountState,useReducer 对应的源码 mountReducer

function mountState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>] {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<BasicStateAction<S>> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue
  ): any));
  return [hook.memoizedState, dispatch];
}
function mountReducer<S, I, A>(reducer: (S, A) => S, initialArg: I, init?: (I) => S) {
  const hook = mountWorkInProgressHook();
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }

  hook.memoizedState = hook.baseState = initialState;

  const queue = (hook.queue = {
    pending: null,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  });

  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue
  ): any));

  return [hook.memoizedState, dispatch];
}

mountState 和 mountReducer 逻辑简单,主要负责创建 hook,初始化 hook 的属性,最后返回 [当前状态,dispatch 函数]

唯一的不同点是 hook.queue.lastRenderedReducer:

  • mountState 使用的内置的 basicStateReducer
  • mountReducer 使用的外部传入自定义 reducer

可见 mountState 是 mountReducer 的一种特殊情况,即 useState 也是 useReducer 一种特殊情况。

在 useState(initialState) 函数内部,设置 hook.memoizedState = hook.baseState = initialState; 初始状态被同时保存到了 hook.baseState, hook.memoizedState 中。

  1. hook.memoizedState: 当前状态
  2. hook.baseState:基础状态,作为合并 hook.baseQueue 的初始值。

最后返回 [hook.memoizedState, dispatch],所以在 function 中使用的是 hook.memoizedState。

状态更新

有如下示例代码:

import { useState } from 'react';

export default function App() {
  const [count, dispatch] = useState(0);
  return (
    <button
      onClick={() => {
        dispatch(1);
      }}
    >
      {count}
    </button>
  );
}

点击 button,通过 dispatch 函数的更新,dispatch 实际就是 dispatchAction:

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

  const update: Udpate<S, A> = {
    lane,
    action,
    eagerReducer: null,
    eagerState: null,
    next: (null, any),
  };

  const pending = queue.pending;
  if (pending === null) {
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;

  const alternate = fiber.alternate;
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  } else {
    // 省略性能优化部分。。。

    scheduleUpdateOnFiber(fiber, lane, eventTime);
  }
}
  1. 创建 update 对象,其中 update.lane 代表优先级。
  2. 将 update 对象添加到 hook.queue.pending 环形链表。
    • 环形链表的特征:为了方便添加新元素和快速拿到队首元素(O(l)),所以 pending 指针指向了链表中最后一个元素。
  3. 发起调度更新:调用 scheduleUpdateOnFiber,进入 reconciler 运作流程中的输入阶段。

从调用 scheduleUpdateOnFiber 开始,进入了 react-reconciler 包,在 fiber 树构造过程中,再次调用 function,这时 useState 对应的函数是 updateState,实际上调用的 updateReducer。

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

  let baseQueue = current.baseQueue;

  const pendingQueue = queue.pending;

  if (pendingQueue !== null) {
    if (baseQueue !== null) {
      const baseFirst = baseQueue.next;
      const pendingFirst = pendingQueue.next;
      baseQueue.next = pendingFirst;
      pendingQueue.next = baseFirst;
    }
    current.baseQueue = baseQueue = pendingQueue;
    queue.pending = null;
  }
  current.baseQueue = baseQueue = pendingQueue;
  queue.pending = null;

  if (baseQueue !== null) {
    const first = baseQueue.next;
    let newState = current.baseState;

    let newBaseState = null;
    let newBaseQueueFirst = null;
    let newBaseQueueLast = null;
    let update = first;

    do {
      const updateLane = update.lane;
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        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;
        }

        currentlyRenderingFiber.lanes = mergeLanes(currentlyRenderingFiber.lanes, updateLane);
        markSkippedUpdateLanes(updateLane);
      } else {
        if (newBaseQueueLast !== null) {
          const clone: Update<S, A> = {
            lane: NoLane,
            action: update.action,
            eagerReducer: update.eagerReducer,
            eagerState: update.eagerState,
            next: (null: any),
          };
          nextBaseQueueLast = newBaseQueueLast.next = clone;
        }
        if (update.eagerReducer === reducer) {
          newState = ((update.eagerState: any): S);
        } else {
          const action = update.action;
          newState = reducer(newState, action);
        }
      }
      udpate = update.next;
    } while (update !== null && update !== first);

    if (newBaseState === null) {
      newBaseState = newState;
    } else {
      newBaseQueueLast.next = (newBaseQueueFirst: any);
    }

    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];
}
  1. 调用 updateWorkInProgressHook 获取 workInProgress 对象。
  2. 链表拼接:将 hook.queue.pending 拼接到 current.baseQueue。
  3. 状态计算
    1. update 优先级不够:加入 baseQueue,等待下一次 render。
    2. update 优先级足够:状态合并。
    3. 更新属性。

性能优化

dispatchAction 函数中,在调用 scheduleUpdateOnFiber 之前,针对 update 对象做了性能优化。

  1. queue.pending 只包含当前 update 时,即当前 update 是 queue.pending 中的第一个 update。
  2. 直接调用 queue.lastRenderedReducer,计算出 update 之后的 state,记为 eagerState。
  3. 如果 eagerState 与 currentState 相同,则直接退出,不用发起调度更新。
  4. 已经挂载到 queue.pending 上的 update 会在下一次 render 时再次合并。
function dispatchAction<S, A>(fiber: Fiber, queue: UpdateQueue<S, A>, action: A) {
  // 省略其他部分代码,只保留性能优化部分代码

  if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
    const lastRenderedReducer = queue.lastRenderedReducer;
    if (lastRenderedReducer !== null) {
      let prevDispatcher;
      const currentState: S = (queue.lastRenderedState: any);
      const eagerState = lastRenderedReducer(currentState, action);
      update.eagerReducer = lastRenderedReducer;
      update.eagerState = eagerState;
      if (is(eagerState, currentState)) {
        return;
      }
    }
  }

  scheduleUpdateOnFiber(fiber, lane, eventTime);
}

参考链接