説明#
- この記事は v18.1.0 を基に分析されています。
 - この記事を読むには、まずReact Hooks: hooks 链表とReact Hooks: useState 分析を読んでください。
 - 他のフックと同様に、マウントとアップデートのフェーズに分かれます。
 - useState とほぼ同じですので、多くの内容は useState を参考にすることができます。
 - 分析は公式デモを基にしています。
 
import { useReducer } from "react";
function init(initialCount) {
  return {count: initialCount};
}
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}
function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
export default Counter;
マウントシーンでの useReducer#
useReducer の初期化時に入ります。

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // フックオブジェクトを生成し、リンクリストに追加します
  const hook = mountWorkInProgressHook();
  let initialState;
  // 初期化関数が存在するかどうかを確認します
  if (init !== undefined) {
    // initを使用してinitialArgを処理します
    initialState = init(initialArg);
  } else {
    // 直接代入します
    initialState = ((initialArg: any): S);
  }
  // 初期値をフックオブジェクトに追加します
  hook.memoizedState = hook.baseState = initialState;
  // useStateと同様に、updateを管理するためのキューがあります
  // 詳細はuseStateの解析を参照してください
  const queue: UpdateQueue<S, A> = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
  /**
   * useStateと同様に、dispatchを返しますが、ここではdispatchReducerActionを返します。
   * useStateはdispatchSetStateを返しますが、ほとんど同じです。次に進んでみましょう
  */
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchReducerAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}
わかりますように、useReducer の実装は useState の実装とほとんど変わりません。もちろん後の部分もほぼ同じです。
useReducer の dispatch のトリガー#
任意の onClick イベントをトリガーすると、dispatchReducerAction に入ります。
dispatchSetState と非常に似ているため、ここでは異なる点のみ説明します。
function dispatchReducerAction<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
) {
  ...
  const lane = requestUpdateLane(fiber);
  const update: Update<S, A> = {
    lane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };
  if (isRenderPhaseUpdate(fiber)) {
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    /**
     * dispatchSetStateと比較すると、
     * dispatchReducerActionは最初のupdateオブジェクトを計算しません
    */
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
  }
  ...
}
他の部分はまったく同じです。
アップデートシーンでの useReducer#
useState のアップデートフェーズと同じです。すでに useState で説明したので、ここでは省略します。
まとめ#
useReducer の実装ロジックは useState とほぼ同じです。唯一の違いは以下の 2 点です。
- useReducer の dispatch は、最初の update オブジェクトが生成される際に期待値(eagerState)を計算しません。
 - useReducer はマウントフェーズで useState と同様に basicStateReducer を生成せず、直接渡された Reducer を使用します。