Skip to main content

コンポーネントのレンダリング処理の仕組み

前のセクション (how begin work works) では、大きな未説明の switch 文が残されていました。このセクションではその詳細を探求します。

作業を始める前に この switch 文を再確認しましょう:

// simplified
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
) {
// previous code

switch (
workInProgress.tag
// case FunctionComponent:
// case ClassComponent:
// case IndeterminateComponent:
// case HostRoot:
// case HostPortal:
// case HostComponent:
// case HostText:
// case Fragment:
// case Mode:
// case ContextConsumer:
// case ContextProvider:
// case ForwardRef:
// case Profiler:
// case SuspenseComponent:
// case MemoComponent:
// case SimpleMemoComponent:
// case LazyComponent:
// case IncompleteClassComponent:
// case DehydratedFragment:
// case SuspenseListComponent:
// case ScopeComponent:
// case OffscreenComponent:
// case LegacyHiddenComponent:
// case CacheComponent:
// case TracingMarkerComponent:
// case HostHoistable:
// case HostSingleton:
) {
}

throw new Error("...");
}
note

このセクションでは各 work tag のレンダリング方法を説明しますが、非常に重要な前提知識として、子要素の reconcile 処理中に次の子要素の alternate(= workInProgress)が作成されることを理解する必要があります。

初回マウント時、workInProgressは alternate となり、この時点でのcurrentは null です。しかし更新時にはcurrentが定義されます。

最初の fiber がいつ作成されるのか疑問に思う場合、前セクションの注記にroot.render()から来た際の 3 番目の fiber 作成について言及されています。これはrenderが受け取った最初の子の alternate となります。

WorkTag に基づくレンダリングの仕組み

beginWorkは fiber のtagに特化したレンダリング関数へ処理をリダイレクトします。

Function Component のレンダリング仕組み

最も一般的なコンポーネント作成方法であるfunction componentsから説明を始めます。

switch (workInProgress.tag) {
case FunctionComponent: {
// 1 Componentは関数コンポーネント
// workInProgressはこのツリーのalternate
// current === nullは初回マウントを意味する
const Component = workInProgress.type;
// 2 pendingPropsはコンポーネントがレンダーする次のprops
const unresolvedProps = workInProgress.pendingProps;
// 3 Componentの型が変更された場合にdefaultPropsを解決
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
// 4 コンポーネントをレンダー
return updateFunctionComponent(
current, // 現在のレンダー済みFiber
workInProgress, // そのalternate(最初はcurrentより先に作成される)
Component, // 関数コンポーネント
resolvedProps, // コンポーネントのprops
renderLanes // レンダーlane
);
}
}

これが関数コンポーネントのレンダリング処理です。updateFunctionComponentに入る前のトップレベルコードを分解してみましょう:

  1. 最初のステップでworkInProgress fiber のtypeを参照
  2. pendingPropsは次に defaultProps を追加する必要があるためunresolvedPropsと呼ばれる
  3. workInProgress.elementTypeworkInProgress.typeと異なる場合、workInProgress.typeから default props を解決
  4. updateFunctionComponentを通じて実際のレンダリング作業を返す
note

関数コンポーネントの初回マウントはFunctionComponentケースを通過せず、IndeterminateComponentを経由します。

React がElementからFiberを作成する際、最初はIndeterminateComponentとしてマークされ、レンダー時にFunctionComponentとして再マークされるためです(後述の関連ケースで説明します)。

関数コンポーネントを再レンダーするupdateFunctionComponent実装を見てみましょう:

// simplified
function updateFunctionComponent(
current: null | Fiber, // 現在のレンダー済みFiber
workInProgress: Fiber, // alternate(進行中の作業)
Component: any, // 関数コンポーネント
nextProps: any, // 新しいprops
renderLanes: Lanes // レンダーlane(DefaultLane、SyncLane、Transition...)
): Fiber | null {
// [...] レガシーなcontext管理

// 1
// このコンポーネント内でcontextを読み取る準備
prepareToReadContext(workInProgress, renderLanes);

// 2
// この関数を最初から待ち望んでいたでしょう 😅
// 注: Indeterminateから関数コンポーネントをレンダーする場合もこの関数を呼び出します
let nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
// これは無視しても構いません
// 気になりますか?これは関数コンポーネントの第2引数です 🙂
context,
renderLanes
);

if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

workInProgress.flags |= PerformedWork;
// 3
// 以前にも見た処理です 😉 そしてこれから何度も見ることになります
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

上記の関数コンポーネントレンダリングの主なステップは以下の通りです:

Prepare to read context

関数コンポーネントをレンダリングする際、useContextフックを使用して最も近いReactContextを subscribe する可能性があります。

useContextの呼び出しは、実際にfiber.dependenciesリンクリストに目的の context を登録します。

レンダリング前、React は このリストを空にし、レンダリング中に再度スタックします(React はsubscribe する context を変更しても気にしません)。

render with hooks

最初にシグネチャと呼び出し方法を確認しましょう:

export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes
): any {
// [Not Native Code]
}

// このように呼び出されます
let nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes
);

実装の要点を見てみましょう:

// simplified
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes
): any {
// モジュール内部状態
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;

// workInProgressのプロパティをリセット(alternate)
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;

// 1
// React Hooksにおいて非常に重要なステップ
ReactCurrentDispatcher.current =
// 初回マウント時
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;

const shouldDoubleRenderInDev = __DEV__ && workInProgress.mode & StrictMode;

// 2
let children = Component(props, secondArg);

// 3
if (didScheduleRenderPhaseUpdateDuringThisPass) {
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg
);
}

// 4
if (shouldDoubleRenderInDev) {
children = renderWithHooksAgain(
workInProgress,
Component,
props,
secondArg
);
}

// 5
finishRenderingHooks(current, workInProgress, Component);

// 6
return children;
}

主なステップの説明:

  1. 関数コンポーネントレンダリング時の最初の重要な処理は適切な dispatcher の設定:

    dispatcher についての詳細は Hooks セクションで説明しますが、このセクションで dispatcher について理解すべき重要な点は、すべてのエクスポートされた hook が次のパターンに従うことです: 現在の dispatcher(コンポーネントレンダリング前に React が設定したもの)を解決し、dispatcher から同じ関数を返します。

    // ほぼ全てのhookはこのパターンに従います
    function useSomeHook() {
    const dispatcher = ReactCurrentDispatcher.current;
    return dispatcher.useSomeHook();
    }
  2. 2 番目のステップでは実際のコンポーネントをレンダリングし、その戻り値を取得します:

    let children = Component(props, secondArg);

    secondArgはこのツリーのfiberRootcontext(またはpendingContext)プロパティです。

    note

    この時点で全ての hook が実行されます

  3. 現在のレンダリングコンポーネントがレンダーフェーズ更新をスケジュールしたか追跡し、該当する場合は再レンダリング: renderWithHooksAgainはネストした更新や無限ループを検出するため、レンダーフェーズ更新がなくなるまでコンポーネントを再レンダリングします。

    renderWithHooksAgain の目的は、ネストした更新や無限ループを追跡し、一部の hooks の動作を変更する異なる dispatcher を設定することです(詳細は hooks セクションで説明します)。

    function renderWithHooksAgain<Props, SecondArg>(
    workInProgress: Fiber,
    Component: (p: Props, arg: SecondArg) => any,
    props: Props,
    secondArg: SecondArg
    ): any {
    let numberOfRerenders = 0;
    let children;
    do {
    // thenablesとuse() hookに関連するコード
    didScheduleRenderPhaseUpdateDuringThisPass = false;

    if (numberOfReRenders >= RE_RENDER_LIMIT) {
    throw new Error(
    "Too many re-renders. React limits the number of renders to prevent " +
    "an infinite loop."
    );
    }

    numberOfReRenders += 1;
    currentHook = null;
    workInProgressHook = null;
    workInProgress.updateQueue = null;

    ReactCurrentDispatcher.current = __DEV__
    ? HooksDispatcherOnRerenderInDEV
    : HooksDispatcherOnRerender;

    children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);

    return children;
    }

    renderWithHooksAgain は、レンダーフェーズ更新がスケジュールされなくなるまでコンポーネントをレンダリングし続けます。

  4. 同様に、StrictModeが有効な場合、別のレンダリングを実行するためrenderWithHooksAgainを再度呼び出します

  5. finishRenderingHooks関数の呼び出し:

    finishRenderingHooks(current, workInProgress, Component);

    この関数は以下を実行します:

    1. ReactCurrentDispatcherContextOnlyDispatcherにリセット
    2. hooks 変数のリセット(currentHook、wipHook)
    3. thenable と use 状態のリセット
    4. 使用された hooks が少ない場合にエラーを送出
  6. renderWithHooksの最終ステップで生成されたchildrenを返却:

    return children;

Reconcile children

コンポーネントをレンダリングして次の子要素を取得した後、前のツリーの子要素との reconciliation を行うタイミングです。

let nextChildren = renderWithHooks(...);

reconcileChildren(current, workInProgress, nextChildren, renderLanes);

return workInProgress.child;

前述のように、reconcile children はコンポーネントがマウント中か更新中かに応じて処理を適切な reconciliation 関数にリダイレクトします。

reconcileChildrenの目的は、次に処理される最初の子要素のalternateを作成することです。

このchild fiber が null の場合(現在のツリーの最下部に到達した場合)、performUnitOfWorkから覚えているように、処理するnext fiber がnullになるとcompleteWorkを呼び出します。

以上が、React 内部での関数コンポーネントのレンダリング処理のステップバイステップの流れです。

How rendering Class Components works

How rendering Indeterminate Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Root works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Portal works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Text works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Fragment works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Mode works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Context Consumer works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Context Provider works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Forward Ref works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Profiler works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Suspense works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Memo Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Lazy Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Incomplete Class Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Dehydrated Fragment works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Suspense List works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Scope Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Offscreen Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Legacy Hidden Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Cache Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Tracing Marker Component works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Hoistable works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.

How rendering Host Singleton works

This section is not available yet. Please fill an issue.

While waiting for the newsletter, you can get notified when new content drops by following me on X.