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の総ざらいをしたいと思います。