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
から直接の最初の子を順にたどり、最下部に到達するまで単一のパスをレンダーします。
このケースでは、beginWork
がnull
ファイバーを返すと、現在のツリーで行うべき作業がなくなったことを意味します。
これが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 つあります:
completeUnitOfWork
内で次に処理するファイバーを決定するために呼び出される- 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 を使用してコンテキストプロバイダーを兼ねる特別な分岐があります。
getChildContext()
はレガシーとマークされており、現在は有効でないかバンドルされていない可能性があります。
この API は使用しないでください。
How completing HostRoot
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 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);
次の節では、レンダーの完了とコミットフェーズについて詳しく見ていきましょう。