説明#
- 本文は v18.1.0 に基づいて分析されています。
- 本文を読む前に React Hooks: hooks リスト、React Hooks: useState 分析 を読む必要があります。
- 他の hooks と同様に、mount と update の段階に分かれます。
- 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})}>
リセット
</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 と同様に、皆さんは update を管理するための queue を持っています
// 詳細は 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 の実装は mount 段階で 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);
}
}
...
}
他は全く同じです。
update シーンでの useReducer#
useState の update 段階と全く同じで、useState の中で既に紹介されていますので、ここでは繰り返しません。
まとめ#
useReducer の実装ロジックは useState とほぼ一致しています。唯一の違いは以下の 2 点です。
- useReducer の dispatch は最初の update オブジェクトを生成する際に期待値(eagerState)を計算しません。
- useReducer は mount 段階で useState のように basicStateReducer を生成するのではなく、渡された Reducer を直接使用します。