Skip to main content

Fiber の作成方法 ⏸️

note

このセクションは ⏸️ break です。前のセクションから続かず、別の重要なテーマについて説明した後、元の流れに戻ります。

このセクションでは、React 内部でどのように、どこで、いつすべての fiber の作成が行われるかを探求します。

これまで多くの fiber を見てきましたが、ここでは基本に戻りながら拡張的な内容を説明します。

最初の fiber の作成方法

最初に作成される fiber は fiberRoot.current プロパティを指します。

簡単な例から始めましょう:

import * as React from "react";
import { createRoot } from "react-dom/client";

// 1
const root = createRoot(document.getElementById("root"));

// 2
root.render(<App />);

function App() {
const rerender = useRerender();
return (
<div>
<h2>Hello, world!</h2>
<h3>Hello again</h3>
<button onClick={rerender}>rerender</button>
</div>
);
}

// コンポーネントの再レンダー用(無視して構いません)
function useRerender() {
return React.useState(0)[1].bind(null, (prev) => prev + 1);
}

ステップ 1(createRoot(container) 呼び出し直後)のツリー構造:

createRoot()直後のツリー構造

ステップ 2(root.render() 呼び出し後、コミット前)では、React は代替ツリーを構築します:

root.render()後、コミット前のツリー構造

この作成処理は、reconciliation の仕組みで見た reconcileChildren 内で実行されます。

コミット後、root.current.alternate が新しい current ツリーになります:

レンダリングとコミット後のツリー構造

この代替ツリーと current ツリーの交換プロセスは、ランタイム中常に発生します。

更新時のツリー構造:

更新時のツリー構造

実際に確認するには、React アプリケーションのブラウザコンソールで以下を実行してください(またはこちらの使用例を含むサンドボックスを利用できます):

// ルートが1つあると仮定
[...window.__REACT_DEVTOOLS_GLOBAL_HOOK__.getFiberRoots(1)][0];

最初の root を取得後、root.current から childsiblingalternate プロパティを確認できます。

alternate の仕組み

alternate とは

alternateFiberNode オブジェクトです。fiber がレンダーされようとする際に作成され、現在の描画済みツリー(current)から生成されます。

React はコミット前までに複数回レンダーを実行することがあります(前述の通り)。

代替(alternate)fiber は親からの reconciliation 中に作成されます:

前の例に戻り、App コンポーネントから更新をトリガーする場合を考えます。

function App() {
const rerender = useRerender();
return (
<div>
<h2>Hello, world!</h2>
<h3>Hello again</h3>
<button onClick={rerender}>rerender</button>
</div>
);
}

SyncLane でトリガーされた更新が即時レンダリングされると仮定します。

これによりまず App コンポーネントの fiber 用に alternate が作成され、レンダーを実行して新しい子要素を取得します。

その後、以下のように reconcileChildren が呼び出されます:

reconcileChildren(
// 新しいツリーのreturnFiber
App_Alternate_Fiber,
// 現在のツリーの最初の子
div_Current_Fiber,
// 新しい子要素: ReactNode(div要素)
{
type: "div",
$$typeof: Symbol("react.element"),
props: {
children: [
// h2要素
{
$$typeof: Symbol("react.element"),
type: "h2",
props: { children: "Hello, world!" },
},
// h3要素
{},
],
},
},
// レンダー用レーン
SyncLane
);

reconcileChildren は実際には最初の子を現在の alternate にアタッチします。

function reconcileChildren(
current, workInProgress, nextChildren, renderLanes
) {
workInProgerss.child = ...
}

alternate の作成方法

fiber と alternate はどちらもFiberNodeのインスタンスです。FiberNodeコンストラクタを使用する唯一のインスタンス化はcreateFiber関数で行われます:

function createFiber(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
}

createFiberは、コードベース全体の他の多くの関数から直接または間接的に呼び出されます。

以下は fiber を直接作成する関数の一部です:

// この関数はprepareFreshStackや、MemoComponentやSuspenseフォールバックなどの
// コンポーネントタグをレンダリングする際に呼び出されます。または、
// bailoutが発生した際に呼ばれるcloneChildFibersからも呼び出されます。
// reconciliation中のuseFiberからも呼び出されます。
function createWorkInProgress(current, pendingProps) {}

// createRootから呼び出されます
function createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride
) {}

// reconciliation中やメモコンポーネントのレンダリング時に、
// createFiberFromElement(element, mode, lanes)から呼び出されます
function createFiberFromTypeAndProps(
type: any, // コンポーネント
key: null | string, // key
pendingProps: any, // nextProps
source, // 開発モードで使用(ファイル名と行番号を含む)
owner: null | Fiber, // 開発モードで使用(fiber)
mode: TypeOfMode, //
lanes: Lanes //
) {}

// 以下の関数はReactFiberモジュール内のcreateFiberFromTypeAndPropsから
// 呼び出されます。各タグごとの実際の作成処理をディスパッチする役割を果たします。
// Reactは誤った抽象化や競合ケースによるバグを避けるため、多くの関数を使用します。
// <明示的なコードが最良>
function createFiberFromFragment(elements, mode, lanes, key) {}
function createFiberFromScope(scope, pendingProps, mode, lanes, key) {}
function createFiberFromProfiler(pendingProps, mode, lanes, key) {}
function createFiberFromSuspense(pendingProps, mode, lanes, key) {}
function createFiberFromSuspenseList(pendingProps, mode, lanes, key) {}
function createFiberFromOffscreen(pendingProps, mode, lanes, key) {}
function createFiberFromLegacyHidden(pendingProps, mode, lanes, key) {}
function createFiberFromCache(pendingProps, mode, lanes, key) {}
function createFiberFromTracingMarker(pendingProps, mode, lanes, key) {}
function createFiberFromText(content, mode, lanes) {}
function createFiberFromHostInstanceForDeletion() {}
function createFiberFromDehydratedFragment(dehydratedNode) {}
function createFiberFromText(content, mode, lanes) {}
function createFiberFromPortal(portal, mode, lanes) {}
function createFiberFromPortal(portal, mode, lanes) {}

// 上記の関数はすべて以下のような構造を持っています:
function createSpecialFiber(pendingProps, mode, lanes, key) {
const fiber = createFiber(SpecialTag, pendingProps, key, mode);
// React内部値に割り当てられたelementTypeは、内部使用専用であり
// Suspenseなどのコンポーネントとしてエクスポートされる可能性があります
fiber.elementType = REACT_SPECIAL_ELEMENT_TYPE; // またはコンポーネントタイプ
fiber.lanes = lanes;
}

fiber のインスタンス化はすべての属性を初期化し、それで完了します。