Skip to main content

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_strictModebooleanルートレベルで StrictMode を有効/無効化
unstable_concurrentUpdatesByDefaultbooleanルートのデフォルト更新モードをコンカレントに設定
unstable_transitionCallbacksTransitionTracingCallbacksトランジション追跡用コールバック(詳細は後述)
identifierPrefixstringReact Flight ルートの識別子プレフィックス
onRecoverableError(error: any) => voidReact がエラーから自動回復した際のコールバック(デモ

TransitionTracingCallbacksの定義はこちらを参照してください。

note

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.");
}

有効なコンテナの種類はこちら:

2. 開発環境での不正なコンテナ警告

開発ビルドでは以下の違反がある場合、警告が表示されます:

  • bodycontainerとして使用(拡張機能やサードパーティライブラリが使用する可能性があり、Reconciliation 問題を引き起こす可能性)
  • 同じcontainerで従来のReactDOM.render(container, element)を既に呼び出している
  • 同じcontainercreateRootを既に呼び出している

これらの問題を避けることが重要です。

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に委譲し、initialChildrennull、ハイドレーションフラグにfalseを設定します。

実際の処理をステップごとに分解していきましょう:

  1. FiberRootNodeインスタンスの作成

    const fiberRoot = new FiberRootNode(
    container, // ホスト要素
    tag, // ConcurrentRoot
    hydrate, // このパスではfalse
    identifierPrefix, // options?.identifierPrefix || ''
    onRecoverableError // options?.onRecoverableError || reportError || console.error
    );

    この作成処理では、多くのプロパティが設定されます。詳細は後述の表で説明しますが、ここでは概要を把握しておきましょう。

  2. HostRootタイプの最初のFiberインスタンス作成

    React の有名な Fiber アーキテクチャについて聞いたことがあるでしょう。ここで最初のFiber が作成されます。

    重要な点はReact Modeの検出です。React はこれを使用して様々なケースでのロジックを決定します。

    // 簡略化版
    const unitializedFiber = new FiberNode(
    HostRoot, // タグ
    null, // pendingProps
    null, // キー
    mode // 推測されたReactモード(Strict Mode、Strict Effects、Concurrent Updatesなど)
    );

    ここまでにFiberRootNodeFiberNodeという 2 つの重要なオブジェクトが作成されました。React の root を作成する際、FiberRootNodeの特別なインスタンスと、それに紐づくFiberNodeが生成されることを理解しておくことが重要です。

  3. FiberNodeFiberRootNodeの相互参照

    fiberRoot.current = unitializedFiber;
    unitializedFiber.stateNode = fiberRoot;
  4. FiberNodememoizedState初期化

    この初期化処理は、React でcache機能が有効な場合に若干変化します。

    // 簡略化版
    uninitializedFiber.memoizedState = {
    element: null, // initialChildren
    isDehydrated: false, // hydrate
    cache: null, // フィーチャーフラグで制御
    };
  5. FiberNodeupdateQueue初期化 この初期化処理により、unintializedFiberupdateQueueプロパティが作成されます:

    unitializedFiber.updateQueue = {
    baseState: fiber.memoizedState, // 上記で作成したもの
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
    pending: null,
    lanes: NoLanes, // 0
    hiddenCallbacks: null,
    },
    callbacks: null,
    };

    各プロパティの役割については後ほど説明します。

  6. 最終的にFiberRootNodeを返却

    return fiberRoot;

5. containerRootとしてマーク

ここで 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] */
danger

_internalRootプロパティは React ドキュメントに記載されておらず、使用すべきではありません。

付録

作成されたオブジェクトの詳細なプロパティに興味があれば、以下の情報を参照してください。

FiberRootNodeとFiberNodeのプロパティ詳細
FiberRootNodeのプロパティ
プロパティ説明
tagnumberConcurrentRoot または LegacyRoot(ルートの種類)
containerInfoElementcreateRootに渡されたcontainer
pendingChildrenany保留中の子要素
currentFiberNodeこのルートの現在の Fiber インスタンス
pingCacheWeakMap<Wakeable, Set<mixed>>Promise とリスナーを保持するキャッシュ
finishedWorkFiber or nullコミット準備が整った進行中作業(HostRoot)
timeoutHandleTimeoutID or -1サスペンド時のフォールバックコミットをスケジュールするタイムアウト ID
cancelPendingCommitnull or () => voidサスペンド中のツリーのコミットスケジュールをキャンセルする関数
contextObject or nullコンテキスト情報
pendingContextObject or null保留中のコンテキスト
nextFiberRoot or null保留中の作業を持つルートのリンクリスト
callbackNodeanyコールバックノード
callbackPriorityLaneコールバックの優先度
expirationTimesLaneMap<number>各レーンの有効期限
hiddenUpdatesLaneMap<Array<ConcurrentUpdate> or null>非表示の更新
pendingLanesLanes保留中のレーン
suspendedLanesLanesサスペンド中のレーン
pingedLanesLanes再開されたレーン
expiredLanesLanes期限切れレーン
finishedLanesLanes完了したレーン
errorRecoveryDisabledLanesLanesエラー回復が無効なレーン
shellSuspendCounternumberシェルのサスペンドカウンター
entangledLanesLanes関連付けられたレーン
entanglementsLaneMap<Lanes>レーンの関連付け情報
identifierPrefixstringReact Flight ルートの識別子プレフィックス
onRecoverableError(error: mixed, errorInfo: {digest?: string, componentStack?: string}) => void回復可能なエラー発生時のコールバック
FiberNodeのプロパティ
プロパティ説明
tagWorkTag (number)Fiber の種類を識別するタグ
keynull or string一意の識別子
elementTypeReactElement.type元の React 要素の type
typeany解決された関数/クラス
stateNodeanyDOM ノードやクラスインスタンスなどの関連オブジェクト
returnFiber or null親 Fiber
childFiber or null最初の子 Fiber
siblingFiber or null兄弟 Fiber
indexnumberリスト内でのインデックス
refRefObjectref オブジェクト
refCleanupnull or () => voidref クリーンアップ関数
pendingPropsany進行中の props
memoizedPropsany確定済み props
updateQueueUpdateQueue保留中の更新のリンクリスト
memoizedStateany確定済み state
dependenciesDependencies or null依存関係情報
modeTypeOfMode (number)Fiber とそのサブツリーの特性を示す数値
flagsFlags (number)Fiber の動作と能力を表す数値
subtreeFlagsFlags (number)子孫 Fiber からマージされたフラグ
deletionsArray<Fiber> or null削除予定の子 Fiber
nextEffectFiber or null次のエフェクト
firstEffectFiber or null最初のエフェクト
lastEffectFiber or null最後のエフェクト
lanesLanes保留中のレーン
childLanesLanes子のレーン
alternateFiber or null代替 Fiber
danger

React 開発経験の中で、ルートオブジェクトやそのプロパティを直接使用したことは一度もありません。React チームも内部プロパティの使用を強く推奨しておらず、これらは変更される可能性があり、使用するとアプリケーションが破損するリスクがあります。

これまでに、私たちは与えられた DOM container内でアプリケーションをレンダーするための FiberRoot オブジェクトを作成しました。

次のセクションでは、root.render()の動作原理について解説します。