Skip to content

7. 훅 업데이트와 dispatchAction ​

이번 챕터에선 useState가 업데이트를 원형 연결 리스트에 적재하고 마운트 시점의 성능 최적화에 대해 알아봅니다.

1. 단계 구분의 기준: currentlyRenderingFiber ​

React는 Render 단계와 Idle 단계를 구분하여 업데이트를 처리합니다.

ReactFiberHooks.js 모듈을 보면, 전역변수로 currentlyRenderingFiber가 선언되어 있습니다. 이름에서 현재 렌더링 중인 Fiber임을 알 수 있으며, useState의 반환값인 setState 함수의 내부 구현을 타고 들어가면, isRenderPhaseUpdate 함수에서 참조됩니다.

jsx
// /react/packages/react-reconciler/src/ReactFiberHooks.js

function isRenderPhaseUpdate(fiber: Fiber): boolean {
  const alternate = fiber.alternate;
  return (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  );
}

function dispatchSetStateInternal<S, A>(
  fiber: Fiber,
  queue: UpdateQueue<S, A>,
  action: A,
  lane: Lane,
): boolean {
  const update: Update<S, A> = {
    // ...
  };

  if (isRenderPhaseUpdate(fiber)) {
    // render phase update
  } else {
    // idle update
  }
};

이 함수는 렌더링 단계와 유휴 단계에 따라 처리 방식이 달라 둘을 구별하기 위한 목적으로 사용됩니다.

  • 렌더링 단계: currentlyRenderingFiber가 비어있지 않음
  • 유휴 단계: currentlyRenderingFiber가 비어있음

2. 업데이트 적재: 원형 연결 리스트 ​

업데이트가 발생하면 리액트는 enqueueRenderPhaseUpdate를 통해 업데이트 객체를 훅의 큐에 담습니다. 이때 원형 연결 리스트(Circular Linked List) 구조를 사용합니다.

jsx
function enqueueRenderPhaseUpdate<S, A>(
  queue: UpdateQueue<S, A>,
  update: Update<S, A>,
): void {
  didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
  const pending = queue.pending;
  if (pending === null) {
    // 첫 업데이트인 경우: 자기 자신을 가리키는 원형 구조 형성
    update.next = update;
  } else {
    // 이미 업데이트가 있는 경우: 기존 리스트의 끝에 삽입
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}

(*원형 연결 리스트 구조를 선택한 이유는 이후 챕터에서 설명합니다.)

2. 불필요한 리렌더링 방지: 즉시 반환 최적화 ​

리액트는 idle 상태에서 업데이트가 발생하고 다음 조건과 같다면 성능 최적화를 수행할 수 있습니다.

최적화 조건 ​

  1. 스케줄링된 작업이 없을 때: 현재 컴포넌트의 업데이트로 인해 Work가 스케줄링 되어있지 않은 상태

  2. 상태 결과값이 이전과 같을 때: 업데이트 결과 값이 동일한 경우

최적화 결과 ​

이 조건이 충족되면 리액트는 scheduleUpdateOnFiber를 호출하지 않고 즉시 반환하여 컴포넌트 함수를 다시 실행하는 과정 자체가 생략되므로 성능을 보존할 수 있습니다.

참고자료 ​