初期構築プロセスの核心
Fiberツリーの構築は2つの主要シナリオで発生します:
- 初回構築:アプリケーション起動時、DOMが未構築の状態。新規ツリーを直接生成します
- 差分更新:既存のUIが存在する場合、新旧Fiberノードを比較して更新を適用します
本解説ではLegacyモード下での初回構築に焦点を当て、コアメカニズムを解説します。
サンプルコードの再構成
class メインコンポーネント extends React.Component {
componentDidMount() {
console.log('メインコンポーネントマウント');
}
render() {
return (
<section className="main">
<header>ヘッダー</header>
<コンテンツ領域 />
</section>
);
}
}
class コンテンツ領域 extends React.Component {
componentDidMount() {
console.log('コンテンツ領域マウント');
}
render() {
return (
<React.Fragment>
<div>アイテム1</div>
<div>アイテム2</div>
</React.Fragment>
);
}
}
export default メインコンポーネント;
レンダリング開始フロー
react-reconcilerパッケージ内の更新処理開始関数が起点となります:
function 更新処理開始(
要素: ReactNode,
コンテナ: ルートオブジェクト,
親コンポーネント: ?ReactComponent,
コールバック: ?Function
): 優先度レベル {
const 現在のFiber = コンテナ.現在のノード;
const イベントタイムスタンプ = タイムスタンプ取得();
// 優先度レベルの計算(車線モデル)
const 優先度 = 優先度レベル計算(現在のFiber);
// 更新オブジェクトの作成とキュー登録
const 更新レコード = 作成更新レコード(イベントタイムスタンプ, 優先度);
更新レコード.ペイロード = { 要素 };
更新キュー追加(現在のFiber, 更新レコード);
// レンダリングスケジューリング
スケジューリング実行(現在のFiber, 優先度, イベントタイムスタンプ);
return 優先度;
}
この処理でHostRootFiber.updateQueueにReact要素が格納され、メモリ構造が初期化されます。
同期レンダリングの実行
Legacyモードでは同期ルート処理実行関数がコアロジックを担当します:
function 同期ルート処理実行(ルート: ファイバールート) {
// レンダリング優先度の決定
const レンダリング優先度 = 次回レンダリング優先度取得(ルート);
// 深さ優先探索によるツリー構築
レンダリングループ同期(ルート, レンダリング優先度);
// 完成ツリーのコミット準備
ルート.完了作業 = ルート.現在のノード.代替;
ルート.完了優先度 = レンダリング優先度;
// DOM反映フェーズへ移行
コミットルート(ルート);
}
作業ループの動作原理
深さ優先探索を実現する作業ループ同期の実装:
function 作業ループ同期() {
while (作業進行中ノード !== null) {
作業単位実行(作業進行中ノード);
}
}
function 作業単位実行(単位作業: Fiber): void {
const 現在ノード = 単位作業.代替;
let 次ノード;
// 探索フェーズ:子ノードの生成
次ノード = 開始作業(現在ノード, 単位作業, サブツリー優先度);
単位作業.メモ化プロップス = 単位作業.保留プロップス;
if (次ノード === null) {
// 帰りがけ順処理の開始
完了作業単位(単位作業);
} else {
作業進行中ノード = 次ノード;
}
}
このループで作業進行中ノードポインタがツリーをトラバースします。
探索フェーズの詳細
開始作業関数はノードタイプごとに処理を分岐します:
function 開始作業(
現在: Fiber | null,
作業中: Fiber,
レンダリング優先度: 優先度レベル
): Fiber | null {
// 優先度のリセット
作業中.レーン = 最高優先度;
switch (作業中.タグ) {
case クラスコンポーネント:
return クラスコンポーネント更新(
現在,
作業中,
作業中.タイプ,
作業中.保留プロップス,
レンダリング優先度
);
case ホストルート:
return ホストルート更新(現在, 作業中, レンダリング優先度);
case ホスト要素:
return ホスト要素更新(現在, 作業中, レンダリング優先度);
// その他のケース
}
}
主な処理ステップ:
- 入力状態から出力状態を計算(
memoizedStateの設定) - 下位のReact要素を取得(
render()実行など) reconcileChildrenで子Fiberノードを生成
帰りがけ順処理の実装
完了作業単位関数がDOM生成を担当します:
function 完了作業単位(単位作業: Fiber): void {
let 完了ノード = 単位作業;
do {
const 親Fiber = 完了ノード.親;
if ((完了ノード.フラグ & 不完全) === 通常フラグ) {
// DOMインスタンスの生成
完了作業(完了ノード);
// 副作用キューの統合
if (親Fiber !== null) {
if (親Fiber.最初の副作用 === null) {
親Fiber.最初の副作用 = 完了ノード.最初の副作用;
}
if (完了ノード.最後の副作用 !== null) {
if (親Fiber.最後の副作用 !== null) {
親Fiber.最後の副作用.次 = 完了ノード.最初の副作用;
}
親Fiber.最後の副作用 = 完了ノード.最後の副作用;
}
// ノード自体の副作用をキューに追加
if (完了ノード.フラグ > 実行済み作業) {
if (親Fiber.最後の副作用 !== null) {
親Fiber.最後の副作用.次 = 完了ノード;
} else {
親Fiber.最初の副作用 = 完了ノード;
}
親Fiber.最後の副作用 = 完了ノード;
}
}
}
// 兄弟ノードの処理へ
if (完了ノード.兄弟 !== null) {
作業進行中ノード = 完了ノード.兄弟;
return;
}
// 親ノードへ戻る
完了ノード = 親Fiber;
作業進行中ノード = 完了ノード;
} while (完了ノード !== null);
}
特にホスト要素タイプでは:
- DOMインスタンスの生成(
createInstance) - 子要素のDOMを親にアペンド
- 属性設定とイベントバインディング
実行フローの可視化
サンプルコードの処理シーケンス:
- HostRootFiberで
メインコンポーネントを生成(Placementフラグ設定) - メインコンポーネントで
section要素を生成 - sectionで
headerを生成 → 文字列子要素のためHostText不要 - headerの完了時にDOMインスタンスを生成
- 次に
コンテンツ領域を処理 → 2つのdivを生成 - 各divで文字列子要素を処理 → DOMインスタンス生成
- 帰りがけ順で親ノードに副作用キューを統合
この過程で副作用キューが構築され、最終的にルートノードに統合されます。キューの順序は深さ優先の帰りがけ順となり、DOM操作の実行順序を決定します。
メモリ構造の変化
構築完了時点で重要な変更:
FiberRoot.finishedWorkが新規Fiberツリーを指すHostRootFiberに副作用キューが構築される- 各HostComponentノードの
stateNodeがDOMインスタンスを参照
この状態でcommitRootが呼び出され、実際のDOM操作が実行されます。