說明#
- 本文基於 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