説明#
- この記事は v18.1.0 を基に分析されています。
- この記事を読むには、まずReact Hooks: hooks 链表とReact Hooks: useState 分析を読んでください。
- 他のフックと同様に、マウントとアップデートのフェーズに分かれます。
- useState とほぼ同じですので、多くの内容は useState を参考にすることができます。
- 分析は公式デモを基にしています。
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;
マウントシーンでの useReducer#
useReducer の初期化時に入ります。
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// フックオブジェクトを生成し、リンクリストに追加します
const hook = mountWorkInProgressHook();
let initialState;
// 初期化関数が存在するかどうかを確認します
if (init !== undefined) {
// initを使用してinitialArgを処理します
initialState = init(initialArg);
} else {
// 直接代入します
initialState = ((initialArg: any): S);
}
// 初期値をフックオブジェクトに追加します
hook.memoizedState = hook.baseState = initialState;
// useStateと同様に、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 の実装とほとんど変わりません。もちろん後の部分もほぼ同じです。
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);
}
}
...
}
他の部分はまったく同じです。
アップデートシーンでの useReducer#
useState のアップデートフェーズと同じです。すでに useState で説明したので、ここでは省略します。
まとめ#
useReducer の実装ロジックは useState とほぼ同じです。唯一の違いは以下の 2 点です。
- useReducer の dispatch は、最初の update オブジェクトが生成される際に期待値(eagerState)を計算しません。
- useReducer はマウントフェーズで useState と同様に basicStateReducer を生成せず、直接渡された Reducer を使用します。