performUnitOfWork
에서 beginWork
가 null을 반환하면 completeUnitOfWork
가 호출된다.
next = beginWork(current, unitOfWork, entangledRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
beginWork
에서는 workInProgress
를 처리하고 다음에 처리할 자식 Fiber를 반환하는데, 반환값이 없다는건 잎 노드로 왔다는 것이다. 우리 예제의 경우 표시된 Fiber에 대해 completeUnitOfWork
가 호출된다. 모두 잎 노드이다.
HostRootFiber
FunctionComponentFiber (App)
HostComponentFiber (div)
HostComponentFiber (p)
FunctionComponentFiber (Link)
HostComponentFiber (a)
HostComponentFiber (br)
HostComponentFiber (button)
HostTextFiber
HostTextFiber
completeUnitOfWork
를 살펴보자. 주석을 보며 completeUnitOfWork
가
Fiber 트리를 어떻게 순회하는지 살펴보자.
function completeUnitOfWork(unitOfWork: Fiber): void {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
let completedWork: Fiber = unitOfWork;
do {
...
const current = completedWork.alternate;
const returnFiber = completedWork.return;
const next = completeWork(current, completedWork, entangledRenderLanes);
...
// sibling fiber가 있으면 workInProgress를 이로 지정하고 리턴한다.
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
// sibling fiber가 없으면 부모 노드로 올라가 반복한다.
completedWork = returnFiber;
} while (completedWork !== null);
...
}
siblingFiber
가 없다는건 현재 노드가 부모 노드의 마지막 자식 노드라는 것이다.
따라서 부모 노드에 대해 completeWork
가 호출되었다면 모든 자식 노드에 대해 이미 completeWork
가 호출되었음을 보장할 수 있다.
이해를 돕기 위해 예제 앱에서 completeWork가 호출되는 순서를 찍어보면 다음과 같다:
HostComponentFiber (a)
FunctionComponentFiber (Link)
HostComponentFiber (br)
HostTextFiber ("click me -")
HostTextFiber ("0")
HostComponentFiber (button)
HostComponentFiber (p)
HostComponentFiber (div)
FunctionComponentFiber (App)
HostRootFiber
completeWork
에서는 마침내! DOM을 실제로 생성한다. 900줄짜리 함수지만 핵심만 살펴보자.
Fiber의 stateNode 필드에 DOM node를 할당한다. ReactFiberConfig에서 필요한 함수를 가져다 쓴다.
import {
createInstance,
...
} from './ReactFiberConfig';
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
popTreeContext(workInProgress);
switch (workInProgress.tag) {
...
case FunctionComponent:
bubbleProperties(workInProgress);
return null;
...
case HostComponent: {
popHostContext(workInProgress);
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
...
} else {
...
if (wasHydrated) {
...
} else {
const rootContainerInstance = getRootHostContainer();
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
markCloned(workInProgress);
// 자식 Fiber들의 DOM node들을 연결한다.
appendAllChildren(instance, workInProgress, false, false);
// workInProgress의 DOM node를 설정한다.
workInProgress.stateNode = instance;
// a 태그의 자식 텍스트는 여기서 처리한다.
if (
finalizeInitialChildren(
instance,
type,
newProps,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
}
...
return null;
}
// 아까 button의 자식 텍스트들은 여기서 처리한다.
case HostText: {
const newText = newProps;
if (current && workInProgress.stateNode != null) {
...
} else {
...
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
...
} else {
markCloned(workInProgress);
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
bubbleProperties(workInProgress);
return null;
}
...
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
'React. Please file an issue.',
);
}
로그인 중...