[Debugging] Initial mount and re-render

Hee Suh·2024년 6월 4일
1
post-thumbnail

⚠️ React@18.2.0을 기반으로 작성되었으며, 최신 버전에서는 구현이 변경되었을 수 있습니다.

💡 Fiber Tree가 initial mount와 re-render 시에 어떻게 구성되는지 개발자 도구를 통해 확인해보자.

1. Initial mount

1.1 renderRootSync()

root.current: FiberNode — 비어있는 Fiber 트리 (child: null)

root.current.alternate: null

1.1 Initial mount — renderRootSync()

1.1 Initial mount — `renderRootSync()`

1.2 prepareFreshStack()

root.current.alternate: FiberNode비어있는 Fiber 트리 생성!

1.2 Initial mount — prepareFreshStack()

1.2 Initial mount — `prepareFreshStack()`

1.3 performConcurrentWorkOnRoot()

root.current.alternate: FiberNodeFiber 트리 완성!

1.3 Initial mount — performConcurrentWorkOnRoot() (1)

1.3 Initial mount — `performConcurrentWorkOnRoot()` (1)

root.current: FiberNode — 여전히 비어있는 Fiber 트리

1.3 Initial mount — performConcurrentWorkOnRoot() (2)

1.3 Initial mount — `performConcurrentWorkOnRoot()` (2)

1.4 commitRoot()

root.current 업데이트 및 화면에 commit 완료!

⇒ workInProgress였던 root.current.alternate과, root.current가 가리키는 트리 서로 switch!

출처: https://jser.dev/2023-07-14-initial-mount/#5-summary출처: https://jser.dev/2023-07-14-initial-mount/#5-summary
출처: https://jser.dev/2023-07-14-initial-mount/#5-summary

root.current: FiberNoderoot.current.alternate가 만든 Fiber 트리 포인트!

1.4 Initial mount — commitRoot() (1)

1.4 Initial mount — `commitRoot()` (1)

root.current.alternate: FiberNoderoot.current가 가리켰던 비어있는 Fiber 트리 포인트!

1.4 Initial mount — commitRoot() (2)

1.4 Initial mount — `commitRoot()` (2)

2. Re-render

2.1 renderRootSync()

root.current: FiberNode — initial mount에서 완성한 Fiber 트리

root.current.alternate: FiberNode — 비어있는 Fiber 트리 (child: null)

2.1 Re-render — renderRootSync()

2.1 Re-render — `renderRootSync()`

2.2 prepareFreshStack()

root.current.alternate: FiberNoderoot.current와 같은 모양의 Fiber 트리로 업데이트!

Cf. prepareFreshStack()에서 호출하는 createWorkInProgress()에서 root.current 트리 복사!

💻 src: ReactFiber.js - createWorkInProgress()

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // We use a double buffering pooling technique because we know that we'll
    // only ever need at most two versions of a tree. We pool the "other" unused
    // node that we're free to reuse. This is lazily created to avoid allocating
    // extra objects for things that are never updated. It also allow us to
    // reclaim the extra memory if needed.
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;
    // Needed because Blocks store data on type.
    workInProgress.type = current.type;

    // We already have an alternate.
    // Reset the effect tag.
    workInProgress.flags = NoFlags;

    // The effects are no longer valid.
    workInProgress.subtreeFlags = NoFlags;
    workInProgress.deletions = null;

  // Reset all effects except static ones.
  // Static effects are not specific to a render.
  workInProgress.flags = current.flags & StaticMask;
  workInProgress.childLanes = current.childLanes;
  workInProgress.lanes = current.lanes;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  // Clone the dependencies object. This is mutated during the render phase, so
  // it cannot be shared with the current fiber.
  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
          lanes: currentDependencies.lanes,
          firstContext: currentDependencies.firstContext,
        };

  // These will be overridden during the parent's reconciliation
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;
  workInProgress.refCleanup = current.refCleanup;

  return workInProgress;
}

2.2 Re-render — prepareFreshStack()

2.2 Re-render — `prepareFreshStack()`

2.3 performConcurrentWorkOnRoot()

root.current.alternate: FiberNode — 트리에 Effect 반영 (e.g. TextNode 0 → 1)

2.3 Re-render — performConcurrentWorkOnRoot() (1)

2.3 Re-render — `performConcurrentWorkOnRoot()` (1)

root.current: FiberNode — initial mount에서 완성한 Fiber 트리 그대로

2.3 Re-render — performConcurrentWorkOnRoot() (2)

2.3 Re-render — `performConcurrentWorkOnRoot()` (2)

2.4 commitRoot()

root.current 업데이트 및 화면에 commit 완료!

⇒ workInProgress였던 root.current.alternate과, root.current가 가리키는 트리 서로 switch!

2.4 Re-render — commitRoot() (1)

2.4 Re-render — `commitRoot()` (1)

root.current.alternate: FiberNoderoot.current가 가리켰던 Fiber 트리 포인트!

2.4 Re-render — commitRoot() (2)

2.4 Re-render — `commitRoot()` (2)

profile
원리를 파헤치는 것을 좋아하는 프론트엔드 개발자입니다 🏃🏻‍♀️

0개의 댓글