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. 分析基於官網 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;

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 一樣,大家都有一個 queue 用來管理 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 的實現在 mount 階段幾乎沒有差別,當然後面也幾乎一樣。

觸發 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 幾乎一致。唯二的不同在於以下兩點

  1. useReducer 的 dispatch 並不會在生成第一個 update 對象時去計算期望值 (eagerState)
  2. useReducer 在 mount 階段並會像 useState 去生成一個 basicStateReducer,而是直接使用傳入的 Reducer
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。