createRoot
の動作原理
React のチュートリアルやドキュメントに従う場合、最初のステップとしてreact-dom/client
からcreateRoot
をインポートし、DOM コンテナナを指定して呼び出した後、返されるroot
オブジェクトのrender
メソッドを呼び出します。
import { App } from "./app";
import { createRoot } from "react-dom/client";
const container = document.getElementById("root");
// これが最初のステップ
const root = createRoot(container);
// 次に2番目のステップ
root.render(<App />);
このセクションではcreateRoot
(最初のステップ)に焦点を当てます。そのシグネチャ、root
オブジェクト作成の目的、そしてその正体について解説します。
シグネチャ
createRoot
は以下のように定義されています(ソースコード参照):
function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions
): RootType {
/* [Not Native code] */
}
createRoot
は DOM ノードを受け取り、RootType
オブジェクトを返します(この DOM ノードはしばしばHostRoot
と呼ばれます)。このオブジェクトはアプリケーションのレンダリングに使用されます。返されるオブジェクトの詳細は後述します。
第 2 引数のoptions
はオプションオブジェクトです。執筆時点でサポートされているオプションは以下の通りです:
プロパティ | 型 | 説明 |
---|---|---|
unstable_strictMode | boolean | ルートレベルで StrictMode を有効/無効化 |
unstable_concurrentUpdatesByDefault | boolean | ルートのデフォルト更新モードをコンカレントに設定 |
unstable_transitionCallbacks | TransitionTracingCallbacks | トランジション追跡用コールバック(詳細は後述) |
identifierPrefix | string | React Flight ルートの識別子プレフィックス |
onRecoverableError | (error: any) => void | React がエラーから自動回復した際のコールバック(デモ) |
TransitionTracingCallbacks
の定義はこちらを参照してください。
unstable_
プレフィックスは実験的機能や開発中の API であることを示します。安定化し React ドキュメントに記載されると、unstable_
プレフィックスが削除され、名前が変更される可能性があります。
確信がない限り実験的 API の使用は避け、使用する場合でも後任の開発者のためにコメントを残すことを推奨します。
React はこの root オブジェクトを使用してアプリケーション全体のレンダリングと状態管理を行います。root オブジェクトはアプリケーションの「根」として重要な役割を果たし、任意の時点でのツリーの状態を把握し操作するための十分な情報を保持しています。
実装の詳細
TL;DR(概要)
if (!isValidContainer(container)) {
throw new Error('createRoot(...): Target container is not a DOM element.');
}
let isStrictMode = false;
let identifierPrefix = '';
// ...other options
if (options) {
if (options.unstable_strictMode === true) {
isStrictMode = true;
}
// ...
}
const fiberRoot = createContainer(
container, // the host element
ConcurrentRoot, // the root type, or RootTag
null, // hydration callbacks
isStrictMode, // options?.unstable_strictMode || false
isConcurrentUpdatesByDefault, // options?.unstable_concurrentUpdatesByDefault || false
identifierPrefix, // options?.identifierPrefix || ''
onRecoverableError, // options?.onRecoverableError || reportError || console.error
transitionCallbacks, // options?.unstable_transitionCallbacks || null
);
// Mark container as root
container.__reactContainer$randomValue = fiberRoot.current;
// Injet ReactDom dispatcher
Dispatcher.current = ReactDOMClientDispatcher;
return new ReactDOMRoot(fiberRoot);
1. container
が有効な React コンテナか確認
if (!isValidContainer(container)) {
throw new Error("createRoot(...): Target container is not a DOM element.");
}
有効なコンテナの種類はこちら:
div
やp
などのDOM 要素- メインページのDocument
- DocumentFragment
- 機能が有効化された React ビルドにおけるコメントノード
2. 開発環境での不正なコンテナ警告
開発ビルドでは以下の違反がある場合、警告が表示されます:
body
をcontainer
として使用(拡張機能やサードパーティライブラリが使用する可能性があり、Reconciliation 問題を引き起こす可能性)- 同じ
container
で従来のReactDOM.render(container, element)
を既に呼び出している - 同じ
container
でcreateRoot
を既に呼び出している
これらの問題を避けることが重要です。
3. 提供されたoptions
のクロージャ化
提供されたオプションを反映する変数を宣言し、デフォルト値にフォールバックします。
// 簡略化版
let isStrictMode = false;
let identifierPrefix = "";
// ...その他のオプション
if (options) {
if (options.unstable_strictMode === true) {
isStrictMode = true;
}
// ...
}
4. スコープ内の情報でcreateContainer
を呼び出し
実際のroot
オブジェクトの作成:
const fiberRoot = createContainer(
container, // ホスト要素
ConcurrentRoot, // ルートタイプ(RootTag)
null, // ハイドレーションコールバック
isStrictMode, // options?.unstable_strictMode || false
isConcurrentUpdatesByDefault, // options?.unstable_concurrentUpdatesByDefault || false
identifierPrefix, // options?.identifierPrefix || ''
onRecoverableError, // options?.onRecoverableError || reportError || console.error
transitionCallbacks // options?.unstable_transitionCallbacks || null
);
生成されるオブジェクトには多くのプロパティがありますが、本セクションの明確化のため、後述するまで詳細は割愛します。createContainer
は実際の作業をcreateFiberRootに委譲し、initialChildren
にnull
、ハイドレーションフラグにfalse
を設定します。
実際の処理をステップごとに分解していきましょう:
FiberRootNode
インスタンスの作成const fiberRoot = new FiberRootNode(
container, // ホスト要素
tag, // ConcurrentRoot
hydrate, // このパスではfalse
identifierPrefix, // options?.identifierPrefix || ''
onRecoverableError // options?.onRecoverableError || reportError || console.error
);この作成処理では、多くのプロパティが設定されます。詳細は後述の表で説明しますが、ここでは概要を把握しておきましょう。
HostRoot
タイプの最初のFiber
インスタンス作成React の有名な Fiber アーキテクチャについて聞いたことがあるでしょう。ここで最初のFiber が作成されます。
重要な点はReact Modeの検出です。React はこれを使用して様々なケースでのロジックを決定します。
// 簡略化版
const unitializedFiber = new FiberNode(
HostRoot, // タグ
null, // pendingProps
null, // キー
mode // 推測されたReactモード(Strict Mode、Strict Effects、Concurrent Updatesなど)
);ここまでに
FiberRootNode
とFiberNode
という 2 つの重要なオブジェクトが作成されました。React の root を作成する際、FiberRootNode
の特別なインスタンスと、それに紐づくFiberNode
が生成されることを理解しておくことが重要です。FiberNode
とFiberRootNode
の相互参照fiberRoot.current = unitializedFiber;
unitializedFiber.stateNode = fiberRoot;FiberNode
のmemoizedState
初期化この初期化処理は、React で
cache
機能が有効な場合に若干変化します。// 簡略化版
uninitializedFiber.memoizedState = {
element: null, // initialChildren
isDehydrated: false, // hydrate
cache: null, // フィーチャーフラグで制御
};FiberNode
のupdateQueue
初期化 この初期化処理により、unintializedFiber
にupdateQueue
プロパティが作成されます:unitializedFiber.updateQueue = {
baseState: fiber.memoizedState, // 上記で作成したもの
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
lanes: NoLanes, // 0
hiddenCallbacks: null,
},
callbacks: null,
};各プロパティの役割については後ほど説明します。
最終的に
FiberRootNode
を返却return fiberRoot;
5. container
をRoot
としてマーク
ここで React は、提供されたcontainer
オブジェクトにロードされた React インスタンス固有のプロパティを追加します。
// 簡略化版
container.__reactContainer$randomValue = fiberRoot.current; // unitializedFiber
6. 現在のReactDispatcher
を注入
React のDispatcher
概念は複雑なため、別途セクションで解説します。ここではReactDOMClientDispatcher
をアタッチします(定義箇所)。
後ほど詳細を説明しますが、React には複数の Dispatcher が存在します。この時点で設定される Dispatcher はサーバーサイドのReactFloat
でも使用されますが、シンプルなクライアントレンダーでは意識する必要はありません。
Dispatcher.current = ReactDOMClientDispatcher;
7. container
にサポート対象イベントリスナーを登録
React は独自のプラグインイベントシステムを実装しています(詳細は別セクション)。この段階で、ルートcontainer
に必要なイベントハンドラーを異なる優先度でアタッチします。
詳細はこちらのコードを参照してください。
8. ReactDOMRoot
インスタンスを返却
コンストラクタはfiberRoot
を_internalRoot
に参照するだけで、root
オブジェクトを検査したことがあれば既に見たことがあるかもしれません。ただし、render メソッドとunmount メソッドも存在します。
function ReactDOMRoot(internalRoot: FiberRoot) {
this._internalRoot = internalRoot;
}
ReactDOMRoot.prototype.render = ... /* [Not Native Code] */
ReactDOMRoot.prototype.unmout = ... /* [Not Native Code] */
_internalRoot
プロパティは React ドキュメントに記載されておらず、使用すべきではありません。
付録
作成されたオブジェクトの詳細なプロパティに興味があれば、以下の情報を参照してください。
FiberRootNodeとFiberNodeのプロパティ詳細
FiberRootNodeのプロパティ
プロパティ | 型 | 説明 |
---|---|---|
tag | number | ConcurrentRoot または LegacyRoot (ルートの種類) |
containerInfo | Element | createRoot に渡されたcontainer |
pendingChildren | any | 保留中の子要素 |
current | FiberNode | このルートの現在の Fiber インスタンス |
pingCache | WeakMap<Wakeable, Set<mixed>> | Promise とリスナーを保持するキャッシュ |
finishedWork | Fiber or null | コミット準備が整った進行中作業(HostRoot) |
timeoutHandle | TimeoutID or -1 | サスペンド時のフォールバックコミットをスケジュールするタイムアウト ID |
cancelPendingCommit | null or () => void | サスペンド中のツリーのコミットスケジュールをキャンセルする関数 |
context | Object or null | コンテキスト情報 |
pendingContext | Object or null | 保留中のコンテキスト |
next | FiberRoot or null | 保留中の作業を持つルートのリンクリスト |
callbackNode | any | コールバックノード |
callbackPriority | Lane | コールバックの優先度 |
expirationTimes | LaneMap<number> | 各レーンの有効期限 |
hiddenUpdates | LaneMap<Array<ConcurrentUpdate> or null> | 非表示の更新 |
pendingLanes | Lanes | 保留中のレーン |
suspendedLanes | Lanes | サスペンド中のレーン |
pingedLanes | Lanes | 再開されたレーン |
expiredLanes | Lanes | 期限切れレーン |
finishedLanes | Lanes | 完了したレーン |
errorRecoveryDisabledLanes | Lanes | エラー回復が無効なレーン |
shellSuspendCounter | number | シェルのサスペンドカウンター |
entangledLanes | Lanes | 関連付けられたレーン |
entanglements | LaneMap<Lanes> | レーンの関連付け情報 |
identifierPrefix | string | React Flight ルートの識別子プレフィックス |
onRecoverableError | (error: mixed, errorInfo: {digest?: string, componentStack?: string}) => void | 回復可能なエラー発生時のコールバック |
FiberNodeのプロパティ
プロパティ | 型 | 説明 |
---|---|---|
tag | WorkTag (number) | Fiber の種類を識別するタグ |
key | null or string | 一意の識別子 |
elementType | ReactElement.type | 元の React 要素の type |
type | any | 解決された関数/クラス |
stateNode | any | DOM ノードやクラスインスタンスなどの関連オブジェクト |
return | Fiber or null | 親 Fiber |
child | Fiber or null | 最初の子 Fiber |
sibling | Fiber or null | 兄弟 Fiber |
index | number | リスト内でのインデックス |
ref | RefObject | ref オブジェクト |
refCleanup | null or () => void | ref クリーンアップ関数 |
pendingProps | any | 進行中の props |
memoizedProps | any | 確定済み props |
updateQueue | UpdateQueue | 保留中の更新のリンクリスト |
memoizedState | any | 確定済み state |
dependencies | Dependencies or null | 依存関係情報 |
mode | TypeOfMode (number) | Fiber とそのサブツリーの特性を示す数値 |
flags | Flags (number) | Fiber の動作と能力を表す数値 |
subtreeFlags | Flags (number) | 子孫 Fiber からマージされたフラグ |
deletions | Array<Fiber> or null | 削除予定の子 Fiber |
nextEffect | Fiber or null | 次のエフェクト |
firstEffect | Fiber or null | 最初のエフェクト |
lastEffect | Fiber or null | 最後のエフェクト |
lanes | Lanes | 保留中のレーン |
childLanes | Lanes | 子のレーン |
alternate | Fiber or null | 代替 Fiber |
React 開発経験の中で、ルートオブジェクトやそのプロパティを直接使用したことは一度もありません。React チームも内部プロパティの使用を強く推奨しておらず、これらは変更される可能性があり、使用するとアプリケーションが破損するリスクがあります。
これまでに、私たちは与えられた DOM container
内でアプリケーションをレンダーするための FiberRoot オブジェクトを作成しました。
次のセクションでは、root.render()
の動作原理について解説します。