Allra Fintech

트리거 / 렌더 / 커밋

상태 변경에서 하이드레이션까지의 렌더링 단계를 파이프라인 관점으로 정리합니다.

학습 목표

  • 상태 변경 직후 화면이 즉시 변경된다고 가정하지 않습니다.
  • trigger → render → commit → paint 흐름을 구분하여 설명할 수 있습니다.
  • “보이는 시점”과 “상호작용 가능한 시점”을 구분할 수 있습니다.
  • SSR/SSG 환경에서 하이드레이션의 의미를 설명할 수 있습니다.

1) 이 문서 범위

이 문서는 React의 렌더링 파이프라인과 상태 반영 타이밍을 다룹니다.

  • React는 UI를 트리 구조로 다룹니다.
  • 상태는 스냅샷(snapshot) 기반으로 읽힙니다.
  • 렌더(Render)와 커밋(Commit)은 분리된 단계입니다.

이 개념은 useState, useEffect, 리렌더링 최적화, SSR/하이드레이션 이해의 기반이 됩니다.

2) React 렌더링은 “트리거 → 렌더 → 커밋 → 페인트”로 진행됩니다

React 공식 용어에 맞추어 정리하면 다음과 같습니다.

1) 트리거(Trigger)

다음 중 하나가 발생하면 렌더가 예약됩니다.

  • setState
  • 부모 props 변경
  • context 값 변경
  • key 변경

중요한 점은 다음과 같습니다.

트리거는 DOM을 직접 변경하지 않습니다. “다음 UI를 계산하라”는 신호입니다.

2) 렌더(Render)

렌더 단계에서 React는:

  • 현재 상태 스냅샷을 읽고
  • 컴포넌트를 다시 호출하며
  • 다음 UI 트리를 계산합니다.

여기서 계산되는 것은 **“다음에 보여야 할 UI의 설계도”**입니다. 이 시점에서는 아직 DOM이 변경되지 않습니다.

핵심 특징은 다음과 같습니다.

  • 렌더는 순수 계산 단계입니다.
  • 같은 입력(state/props/context)이라면 같은 출력이 나와야 합니다.
  • 렌더 단계에서 DOM을 읽거나 변경해서는 안 됩니다.

상태는 스냅샷입니다

렌더 시점에서 읽는 state는 그 렌더의 스냅샷 값입니다.

setCount(count + 1);
console.log(count); // 이전 값

그 이유는 다음과 같습니다.

  • setState는 즉시 값을 변경하는 동작이 아닙니다.
  • 다음 렌더에서 사용할 새 값을 예약합니다.

3) 커밋(Commit)

렌더 계산이 끝나면 React는:

  • 이전 트리와 다음 트리를 비교하고
  • 실제로 변경된 부분만
  • DOM에 반영합니다.

이 단계에서 실제 DOM이 수정됩니다.

이때 수행되는 작업은 다음과 같습니다.

  • DOM 노드 생성/삭제
  • 속성 변경
  • 이벤트 핸들러 연결
  • ref 갱신
  • useLayoutEffect 실행

4) 페인트(Paint)

브라우저가 실제 픽셀을 화면에 그리는 단계입니다.

React의 책임은 커밋까지이며, 페인트는 브라우저 렌더링 파이프라인의 영역입니다.

3) 렌더와 트리

React는 UI를 트리 구조로 다룹니다.
개념 이해를 위해 구조를 두 단계로 같이 보겠습니다.

1. 사람이 이해하기 쉬운 구조

App
 ├─ Header
 ├─ Sidebar
 └─ Content
     ├─ List
     └─ Detail

2. React 내부 모델 형태(개념 단순화)

{
  type: 'App',
  props: {},
  children: [
    { type: 'Header', props: {}, children: [] },
    { type: 'Sidebar', props: {}, children: [] },
    {
      type: 'Content',
      props: {},
      children: [
        { type: 'List', props: {}, children: [] },
        { type: 'Detail', props: {}, children: [] },
      ],
    },
  ],
}

상태 변경이 발생하면 해당 노드부터 하위 트리까지 다시 계산됩니다. 트리 구조가 달라지면(조건부 렌더링 변화 등) 마운트/언마운트가 발생합니다.

key가 중요한 이유

목록에서 key가 불안정하면 다음 문제가 발생합니다.

  • React가 동일 노드를 다른 것으로 인식합니다.
  • 불필요한 마운트/언마운트가 발생합니다.
  • 내부 상태가 초기화되거나 DOM이 재생성됩니다.

key는 요소의 “동일성(identity)”을 보장하는 장치입니다.

5) Paint 단계

페인트는 커밋 바깥의 브라우저 렌더링 단계입니다.

  • React는 커밋까지 책임집니다.
  • 브라우저가 최종 픽셀을 그리는 단계는 Paint입니다.

즉,

  • “DOM 반영 가능성”은 Commit 이후
  • “픽셀 표시”는 Paint 이후

로 나눠 이해하면 사용자 체감이 정확히 설명됩니다.

6) 자주 헷갈리는 오해

  • setState를 호출했는데 화면이 안 바뀐다.”
    • 트리거/렌더/커밋/페인트 전체가 아직 끝나지 않았을 수 있습니다.
  • “DOM이 바로 바뀐다.”
    • React는 계산(렌더)과 적용(커밋)을 분리합니다.

7) 보이는 시점과 상호작용 가능한 시점

CSR (Client-Side Rendering)

  1. JS 실행
  2. Render
  3. Commit
  4. Paint
  5. 즉시 상호작용 가능

SSR / SSG

  1. 서버에서 HTML 생성
  2. 브라우저에 먼저 표시됨(보이는 시점)
  3. JS 로드
  4. 하이드레이션
  5. 상호작용 가능

SSR/SSG에서는 보이는 순간과 상호작용 가능한 순간이 다를 수 있습니다. 하이드레이션이 끝나기 전에는 이벤트 바인딩이 완전하지 않을 수 있습니다.

8) 하이드레이션 정합성

하이드레이션은 서버 HTML과 클라이언트 첫 렌더 결과를 맞추는 과정입니다.

마크업 불일치

  • 서버와 클라이언트 렌더 결과가 다르면 정합성 문제가 발생할 수 있습니다.
  • 조건 분기, 랜덤 값, 시간 의존 값이 흔한 원인입니다.

브라우저 API의 조기 사용

const width = window.innerWidth // SSR에서는 위험

렌더 단계는 서버 렌더링 경로에서도 실행될 수 있으므로, 브라우저 전용 API는 useEffect/useLayoutEffect 같은 Effect에서 다루는 것이 안전합니다.

9) 성능 관점에서의 렌더/커밋

렌더 비용이 커지는 경우

  • 렌더 안에서 무거운 계산을 수행
  • 상위 트리에서 상태를 과도하게 공유
  • 불필요한 재렌더 반복

커밋 비용이 커지는 경우

  • DOM 변경 범위가 크게 잡힘
  • 대량 리스트 재정렬
  • 불안정한 key 사용

체감 성능은 렌더 비용 + 커밋 범위의 합으로 결정됩니다.

10) 운영 체크리스트

  • 상태 변경 시 다시 계산되는 트리 범위를 추적할 수 있는가?
  • Render에 부수 효과가 들어가지 않도록 관리하고 있는가?
  • key를 안정적으로 관리하고 있는가?
  • SSR/SSG에서 랜덤/시간 의존 값으로 정합성을 깨뜨리고 있지 않은가?
  • “보임”과 “클릭 가능” 시점을 분리해 설명할 수 있는가?

11) 핵심 요약

  • setState는 DOM을 즉시 변경하지 않습니다.
  • React는 먼저 Render로 다음 UI를 계산하고,
  • Commit으로 DOM에 적용하고,
  • 브라우저가 Paint로 화면을 그립니다.

참고