Skip to main content

completeWork の仕組み

数節前で、React を使用してアプリケーションをレンダーしました。このプロセスは以下のコードと非常に似ています:

// simplified
function workLoopSync() {
// ファイバー間でyieldする必要があるかチェックせずに作業を実行
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}

function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;

const next = beginWork(current, unitOfWork, renderLanes);

unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 新しい作業が生成されない場合、現在の作業を完了
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}

以前の節で述べたように、React の work loop はfiberRootから直接の最初の子を順にたどり、最下部に到達するまで単一のパスをレンダーします。

このケースでは、beginWorknullファイバーを返すと、現在のツリーで行うべき作業がなくなったことを意味します。

これがcompleteUnitOfWork関数の役割です:次にレンダーするコンポーネントを推論します。

次のファイバーの選択方法を詳細に見ていきましょう。

関数定義

function completeUnitOfWork(unitOfWork: Fiber): void {}

completeUnitOfWorkは、コンポーネントの完了した作業を参照するworkInProgress変数と共に呼び出されます。

実装の詳細

この関数の目的は、次に処理するファイバーを決定することです。

以下は簡略化された実装です:

// simplified
let completeWork = unitOfWork;

do {
const current = completeWork.alternate;
const returnFiber = completedWork.return;
// completeWorkは実際の実装を含む巨大な関数です
// 詳細は後ほど説明します
const next = completeWork(current, completeWork, renderLanes);

// 処理が必要な作業が残っていると判断された場合
if (next !== null) {
workInProgress = next;
return;
}

// 次の作業がない場合、siblingを取得
const siblingFiber = completeWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}

// それもない場合、completeWorkのreturnFiberを取得
completedWork = returnFiber;
workInProgress = completedWork;
} while (completeWork !== null);
//...

completeUnitOfWorkは sibling を通過しながらツリーを遡り続けます。

重要な点として、sibling に移動する際、performUnitOfWorkは再び最下部に到達するまでその children を通過し、その後completeUnitOfWorkを使用して遡ります。

ただし、completeUnitOfWork自体がすべての作業を行うわけではありません。実際の処理は約 1000 行に及ぶcompleteWork関数で行われます。

この関数を確認することが重要な理由は 2 つあります:

  1. completeUnitOfWork内で次に処理するファイバーを決定するために呼び出される
  2. null が返された場合のフォールバックとして、sibling や parent を探索するため

completeWork の動作原理

completeWorkは大規模なswitch statementを含む巨大な関数です。

シグネチャ

function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler: {
// .... 他のタグ
}
}
}

completeWorkは完了した作業のタグに基づいて処理を実行します。

FunctionComponent の完了処理

Function コンポーネントは以下のいずれかとして扱われます:
IndeterminateComponent,LazyComponent,SimpleMemoComponent,FunctionComponent,
ForwardRef,Fragment,Mode,Profiler,ContextConsumer,MemoComponent

具体的な処理内容:

switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
bubbleProperties(workInProgress);
return null;
}

bubblePropertiesは完了した作業に対して呼び出され、
子要素の flags を親要素(完了した作業)にマージする役割を果たします:

// simplified
function bubbleProperties(completedWork: Fiber) {
let subtreeFlags = NoFlags;
let newChildLanes = NoLanes;
let child = completedWork.child;

while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes)
);

subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;

child.return = completedWork;
child = child.sibling;
}

completedWork.subtreeFlags |= subtreeFlags;
completedWork.childLanes = newChildLanes;
}

How completing ClassComponent works

ClassComponentの作業完了処理はFunctionComponentと同様で、プロパティのバブルアップを行います(ほぼすべてのケースで適用されます)。

switch (workInProgress.tag) {
// ...
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
// FunctionComponentと同じ処理
bubbleProperties(workInProgress);
return null;
}
// ...
}

ClassComponents には、レガシーなgetChildContext() API を使用してコンテキストプロバイダーを兼ねる特別な分岐があります。

danger

getChildContext()はレガシーとマークされており、現在は有効でないかバンドルされていない可能性があります。 この API は使用しないでください。

How completing HostRoot works

Switch case

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 other tags work

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.

Recap

すべての作業が完了し、ツリーの各ノードの最下部までレンダーが到達すると、performConcurrentWorkOnRootはレンダーの完了とrootのコミットに進みます。

次のコードを確認してください。以前に見たものですが、次の節に備えて記憶をリフレッシュしておくと良いでしょう。

// simplified to only include relevant things
function performConcurrentWorkOnRoot(
root: FiberRoot,
didTimeout: boolean
): RenderTaskFn | null {
// previous code

const exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);

if (exitStatus !== RootInProgress) {
do {
if (exitStatus === RootDidNotComplete) {
markRootSuspended(root, lanes);
} else {
const finishedWork: Fiber = root.current.alternate;

if (
renderWasConcurrent &&
!isRenderConsistentWithExternalStores(finishedWork)
) {
exitStatus = renderRootSync(root, lanes);
continue;
}
if (exitStatus === RootErrored) {
// ...
}
if (exitStatus === RootFatalErrored) {
// ...
throw fatalError;
}

root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
}
break;
} while (true);
}
}

workLoop 全体は、レンダーレーンに応じてrenderRootSyncまたはrenderRootConcurrent内で発生します。

すべての作業が完了すると、この段階で重要なのは以下のコード、特にfinishConcurrentRenderです。

root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, finishedWork, lanes);

次の節では、レンダーの完了とコミットフェーズについて詳しく見ていきましょう。