Reactの内部実装を見ていくDeep DiveシリーズのuseStateの初期レンダリング編です。
React の useState()はみなさんご存知かと思います。
今回は initial render(mount)時に、 useState()が内部的にどのように動作するのかを、 ソースコードを見ながら解明していきます。
initial render はそこまで複雑ではありません。
これが useState の本体になります。
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const hook = mountStateImpl(initialState);
const queue = hook.queue;
const dispatch: Dispatch<BasicStateAction<S>> = (dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
): any);
queue.dispatch = dispatch;
return [hook.memoizedState, dispatch];
}
引数にある、initialState: (() => S) | S
は、useState()
の引数に渡された値です。
関数だった場合、mountStateImpl
で実行され、state の初期値が決定されます。
return している値に注目してください。useState()
で返される、おなじみの構文になっています。
mountStateImpl
内で、新しい Hook の作成されたり state の初期化がされたりしています。
まず、mountStateImpl
の実装を見ていきましょう。
function mountStateImpl<S>(initialState: (() => S) | S): Hook {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// $FlowFixMe[incompatible-use]: Flow doesn't like mixed types
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
return hook;
}
先ほど、言ったように、
if (typeof initialState === "function") {
initialState = initialState();
}
で、initialState
が関数だった場合、実行されます。
続いて、queue の部分について注目します。
const queue: UpdateQueue<S, BasicStateAction<S>> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
};
hook.queue = queue;
React はすぐには状態更新されません。
更新は、更新キューに入れられ、非同期で処理されます。
理由としては更新の優先順位が異なる可能性があるためです。
更新の優先順位については、React Lane という概念が存在していて、それによって決定されます。
mountStateImpl
にmountWorkInProgressHook
という関数があったと思いますが、
mountWorkInProgressHook
で新しい Hook を作成しています。
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
とりあえず Hook の型をこんな感じです。
export type Hook = {
memoizedState: any;
baseState: any;
baseQueue: Update<any, any> | null;
queue: any;
next: Hook | null;
};
それぞれのプロパティの意味は以下の通りです。
hook.memoizedState: メモリに保持されているローカルな状態。
hook.baseState: hook.baseQueue 内のすべてのアップデートオブジェクトがマージされた後の状態。
hook.baseQueue: 現在のレンダリング優先度よりも高いものだけを含む、アップデートオブジェクトの循環的なチェーン。
hook.queue: 優先度の高いすべてのアップデートオブジェクトを含む、アップデートオブジェクトの循環的なチェーン。
hook.next: 次のポインタ、チェーンの次のフックを指します。
引用元: Hook 原理(概览)
workInProgressHook
は、進行中の Fiber に追加される新しい Hook のリストです。
ざっくりですが、Fiber とは コンポーネントに対応するデータを保持するインスタンスです。
各コンポーネント は必ずcurrnet
とworkInProgress
の 2 つのインスタンスが存在します。
current
は、現在のコンポーネントの状態を保持しています。
workInProgress
は、これから状態更新が反映される Fiber です。
とりあえず、Hook の作成までコードを追っていきました。
Fiber や Lane など新しい概念が出てきましたが、今回は深くは触れませんでした。
次回は、setState 内の実装を見ていこうと思います。