説明#
- この記事は 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 を使用します。