React Hooks: useReducer 分析

说明

本文基于 v18.1.0 进行分析。

阅读本文需先阅读 React Hooks: hooks 链表、React Hooks: useState 分析

与其他 hooks 相同，都会分为 mount 与 update 阶段

与 useState 几乎一致，因此很多内容可以直接参考 useState

分析基于官网 demo

Copy
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

dispatch({type: 'decrement'})}>-
dispatch({type: 'increment'})}>+
</>
);
}

export default Counter;

mount 场景下的 useReducer

初始化对 useReducer 打点，会进入

Copy
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch] {
// 生成 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 = (queue.dispatch = (dispatchReducerAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}

可以看到，useReducer 的实现与 useState 的实现在 mount 阶段几乎没有差别，当然后面也几乎一样。

触发 useReducer 的 dispatch

对随意一个 onClick 打点，触发点击事件后会进入 dispatchReducerAction。

由于与 dispatchSetState 实在是太过相似，因此这里只介绍不同。

Copy
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 几乎一致。唯二的不同在于以下两点

useReducer 的 dispatch 并不会在生成第一个 update 对象时去计算期望值 (eagerState)

useReducer 在 mount 阶段并会像 useState 去生成一个 basicStateReducer，而是直接使用传入的 Reducer