Reactのアーキテクチャを知る useState編3
こんにちは、noyanです。React内部実装アドベントカレンダーの4日目です。昨日集中しすぎてうまく頭が働かないようなので、文章が荒かったらすみません。
引き続きReactのuseStateフックの内部実装を読み解いていきます。
これまで、マウント時のuseState()の挙動とsetStateにバインドされる関数を見てきました。今回は、setStateが呼び出された時、actionがどうやってstateに反映されるのかを見ていきます。
この記事が読み終わった時、setState(prev => prev+1)がどう処理されるのかを理解できることをゴールとします。
queueの行き先
setStateの正体であるdispatchSetStateは、前回の記事で見たところhook.queueにactionをenqueueしていました。このhook.queueはどの関数でstateに追加されるのでしょうか。
queueの更新はrerenderReducerで行われます。queueの構造を思い出しながら読んでください。
function rerenderReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
// This is a re-render. Apply the new render phase updates to the previous
// work-in-progress hook.
const dispatch: Dispatch<A> = (queue.dispatch: any);
// queue.pendingは一番最後の更新、pending.nextは最初の更新が入っていた
const lastRenderPhaseUpdate = queue.pending;
// hooks.memoizedStateは現在DOMに反映されているhookの値
let newState = hook.memoizedState;
if (lastRenderPhaseUpdate !== null) {
// The queue doesn't persist past this render pass.
queue.pending = null;
const firstRenderPhaseUpdate = lastRenderPhaseUpdate.next;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update.
const action = update.action;
// setState(action)に等しい
newState = reducer(newState, action);
// 次のupdateに行き、循環リストが一周するまでnewStateを更新し続ける。
update = update.next;
} while (update !== firstRenderPhaseUpdate);
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;
// Don't persist the state accumulated from the render phase updates to
// the base state unless the queue is empty.
if (hook.baseQueue === null) {
hook.baseState = newState;
}
queue.lastRenderedState = newState;
}
return [newState, dispatch];
}
stateの更新だけに限って話すと、以下が概要です。
do {
newState = basicStateReducer(newState, update.action);
update = update.next;
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
つまり、setStateで入れられたqueueはrerenderReducerによって順番に適応され、新しい値がhook.memoizedStateに代入されます。useStateで返されるstateはhook.memoizedStateを参照しているので、コンポーネントが再計算されたときにこの値が取得できるわけです。
この再計算がどこで行われるかというと、初日の記事で読んだrenderWithHooksです。明日はrenderWithHooksを中心にuseStateの総ざらいをしたいと思います。