说明#
- 本文基于 v18.1.0 进行分析。
- 阅读本文需先阅读 React Hooks: hooks 链表、React Hooks: useState 分析
- 与其他 hooks 相同,都会分为 mount 与 update 阶段
- 与 useState 几乎一致,因此很多内容可以直接参考 useState
- 分析基于官网 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 打点,会进入
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 几乎一致。唯二的不同在于以下两点
- useReducer 的 dispatch 并不会在生成第一个 update 对象时去计算期望值 (eagerState)
- useReducer 在 mount 阶段并会像 useState 去生成一个 basicStateReducer,而是直接使用传入的 Reducer