banner
IWSR

IWSR

我永远喜欢志喜屋梦子!

React Hooks: useReducer Analysis

Explanation#

  1. This article is based on v18.1.0.
  2. To read this article, you need to read React Hooks: hooks chain and React Hooks: useState analysis first.
  3. Like other hooks, it is divided into mount and update phases.
  4. It is almost identical to useState, so many contents can be directly referenced from useState.
  5. The analysis is based on the official demo.
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 in the mount phase#

When initializing useReducer, it will enter

image

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  // Create a hook object and attach it to the linked list
  const hook = mountWorkInProgressHook();
  let initialState;
  // Check if there is an initialization function
  if (init !== undefined) {
    // Process initialArg based on init
    initialState = init(initialArg);
  } else {
    // Assign directly
    initialState = ((initialArg: any): S);
  }
  // Mount the initial value to the hook object
  hook.memoizedState = hook.baseState = initialState;
  // Like useState, they both have a queue to manage updates
  // You can refer to the analysis of useState for details, skipping here
  const queue: UpdateQueue<S, A> = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  };
  hook.queue = queue;
  /**
   * Like useState, dispatch is thrown out, but here it is dispatchReducerAction, useState throws
   * dispatchSetState, which is similar, let's take a look next
  */
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchReducerAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

As you can see, the implementation of useReducer is almost identical to useState in the mount phase, and it is almost the same later.

Triggering dispatch of useReducer#

After clicking on any onClick, it will enter dispatchReducerAction.

Since it is too similar to dispatchSetState, only the differences will be explained here.

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 {
    /**
     * Compared with dispatchSetState, it can be found that
     * dispatchReducerAction does not calculate the expected value (eagerState) for the first generated update
    */
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
  }

  ...
}

The rest is exactly the same.

useReducer in the update phase#

The update phase of useReducer is exactly the same as useState, which has been explained in useState, so it will not be repeated here.

Summary#

The implementation logic of useReducer is almost identical to useState. The only two differences are:

  1. The dispatch of useReducer does not calculate the expected value (eagerState) when generating the first update object.
  2. useReducer does not generate a basicStateReducer like useState in the mount phase, but directly uses the passed Reducer.
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.