Skip to content

9. 렌더 페이즈의 업데이트 ​

이번 챕터에선 리액트 렌더링 중 setState 호출 시 업데이트가 임시 저장되고 소비되는 내부 메커니즘을 파헤칩니다.

1. 동작 원리 및 최적화 ​

idle 상태에서와는 다르게 Work가 진행 중인 상태에서 setState가 호출되면, React는 다음과 같은 방식으로 처리합니다.

  • Work 등록 생략: 이미 렌더링이 진행 중이므로 새로운 스케줄링(scheduleUpdateOnFiber)을 하지 않습니다.
  • 즉각적인 소비: 발생한 업데이트는 다음 '패스(Pass)'에서 즉시 소비됩니다.
  • 재귀 제한: 무한 루프 방지를 위해 RE_RENDER_LIMIT = 25 상수를 사용하여 재호출 횟수를 엄격히 제한합니다.

2. 업데이트 저장 메커니즘 ​

enqueueRenderPhaseUpdate 함수에서 업데이트 객체의 링크드 리스트 구조로 저장하는 방식을 확인할 수 있습니다.

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

function enqueueRenderPhaseUpdate<S, A>(queue: UpdateQueue<S, A>, update: Update<S, A>): void {
  ...

  const pending = queue.pending;
  if (pending === null) {
    // 2. 첫 업데이트라면 자신을 가리키는 순환 구조 생성
    update.next = update;
  } else {
    // 3. 기존 업데이트가 있다면 링크드 리스트로 연결 (Circular Linked List)
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;
}

업데이트를 소비할 때는 가장 오래된 것부터 순서대로 계산해야 하기 때문에 pending.next부터 시작해서 고리를 따라가며 상태를 업데이트합니다.

3. 업데이트 소비 과정 및 25회 제한 루프 ​

컴포넌트가 처음 호출된 후, 만약 내부에서 setState가 발생했다면 React는 아래 루프에 진입합니다.

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

function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
  let numberOfReRenders = 0;
  let children;
  
  do {
    // 1. 현재 패스의 업데이트 플래그 초기화
    didScheduleRenderPhaseUpdateDuringThisPass = false;

    // 2. 무한 루프 체크 (25회 제한)
    if (numberOfReRenders >= RE_RENDER_LIMIT) {
      throw new Error('Too many re-renders. React limits the number of renders to prevent an infinite loop.');
    }
    numberOfReRenders += 1;

    // 3. 훅 상태 초기화 (처음부터 다시 계산하기 위함)
    currentHook = null;
    workInProgressHook = null;
    
    // 4. 디스패처를 'Rerender' 전용으로 교체
    ReactSharedInternals.H = HooksDispatcherOnRerender;

    // 5. 컴포넌트 재호출
    children = Component(props, secondArg);
    
    // 6. 컴포넌트 실행 중 다시 setState가 불렸다면(flag가 true가 됨) 루프 반복
  } while (didScheduleRenderPhaseUpdateDuringThisPass);

  return children;
}

참고자료 ​