Skip to content

React Hooks: useReducer 分析 #10

@IWSR

Description

@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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions