banner
IWSR

IWSR

我永远喜欢志喜屋梦子!

React Hooks: useReducer の分析

説明#

  1. 本文は v18.1.0 に基づいて分析されています。
  2. 本文を読む前に React Hooks: hooks リストReact Hooks: useState 分析 を読む必要があります。
  3. 他の hooks と同様に、mount と update の段階に分かれます。
  4. useState とほぼ一致するため、多くの内容は useState を直接参照できます。
  5. 分析は 公式デモ に基づいています。
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})}>
        リセット
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

export default Counter;

mount シーンでの useReducer#

useReducer の初期化ポイントに入ります。

image

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // hook オブジェクトを生成し、リンクリストに追加
  const hook = mountWorkInProgressHook();
  let initialState;
  // 初期化関数が存在するかどうかを判断
  if (init !== undefined) {
    // init に基づいて initialArg を処理
    initialState = init(initialArg);
  } else {
    // 直接代入
    initialState = ((initialArg: any): S);
  }
  // 初期値を hook オブジェクトにマウント
  hook.memoizedState = hook.baseState = initialState;
  // useState と同様に、皆さんは update を管理するための queue を持っています
  // 詳細は 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 の実装は mount 段階で 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);
    }
  }

  ...
}

他は全く同じです。

update シーンでの useReducer#

useState の update 段階と全く同じで、useState の中で既に紹介されていますので、ここでは繰り返しません。

まとめ#

useReducer の実装ロジックは useState とほぼ一致しています。唯一の違いは以下の 2 点です。

  1. useReducer の dispatch は最初の update オブジェクトを生成する際に期待値(eagerState)を計算しません。
  2. useReducer は mount 段階で useState のように basicStateReducer を生成するのではなく、渡された Reducer を直接使用します。
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。