트리거 / 렌더 / 커밋
상태 변경에서 하이드레이션까지의 렌더링 단계를 파이프라인 관점으로 정리합니다.
학습 목표
- 상태 변경 직후 화면이 즉시 변경된다고 가정하지 않습니다.
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
└─ Detail2. 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)
- JS 실행
- Render
- Commit
- Paint
- 즉시 상호작용 가능
SSR / SSG
- 서버에서 HTML 생성
- 브라우저에 먼저 표시됨(보이는 시점)
- JS 로드
- 하이드레이션
- 상호작용 가능
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로 화면을 그립니다.