Virtual DOM은 실제 DOM 구조를 JavaScript 객체로 표현한 가벼운 사본입니다. React는 UI 변경이 발생하면 실제 DOM을 직접 수정하는 대신, Virtual DOM을 먼저 업데이트하고 이전 Virtual DOM과 비교하여 실제로 바뀐 부분만 DOM에 반영합니다.
React 엘리먼트가 바로 Virtual DOM의 기본 단위입니다.
// React 엘리먼트 = Virtual DOM의 노드
{
type: 'div',
props: {
className: 'container',
children: [
{ type: 'h1', props: { children: '제목' } },
{ type: 'p', props: { children: '내용' } }
]
}
}
실제 DOM(Document Object Model)은 HTML 문서를 트리 구조로 표현하며, 브라우저가 직접 관리합니다. DOM 조작은 브라우저의 레이아웃 계산, 페인팅 등 렌더링 파이프라인 전체를 촉발하므로 비용이 큽니다.
DOM 조작 → 레이아웃 재계산(Reflow) → 화면 다시 그리기(Repaint)
Virtual DOM은 순수한 JavaScript 객체이므로 조작 비용이 매우 낮습니다. React는 변경 계산을 Virtual DOM에서 수행하고, 그 결과(최소한의 변경 사항)만 실제 DOM에 적용합니다.
React는 이전 Virtual DOM 트리와 새 Virtual DOM 트리를 비교하는 과정을 Diffing이라고 합니다. 일반적인 트리 비교 알고리즘은 O(n³)의 복잡도를 가지지만, React는 두 가지 가정을 통해 O(n)으로 줄였습니다.
가정 1: 타입이 다른 엘리먼트는 다른 트리를 만든다.
부모 엘리먼트의 타입이 바뀌면(div → section) React는 기존 트리를 전부 버리고 새로 만듭니다.
이전: <div><Counter /></div>
새로운: <section><Counter /></section>
→ Counter가 언마운트되고 새로 마운트됨
가정 2: key prop으로 자식 엘리먼트를 안정적으로 식별한다.
리스트 렌더링에서 key를 지정하면 React가 각 항목을 추적하여 불필요한 재생성을 방지합니다.
Reconciliation은 Virtual DOM Diffing을 통해 실제 DOM에 어떤 변경을 적용할지 결정하는 전체 과정입니다.
이전 Virtual DOM ─┐
├→ Diff 계산 → 변경 목록 → 실제 DOM 업데이트
새 Virtual DOM ───┘
React는 트리를 루트에서부터 깊이 우선 탐색으로 비교합니다. 같은 위치에 같은 타입의 엘리먼트가 있으면 props만 업데이트하고, 타입이 다르면 해당 서브트리를 교체합니다.
React 16에서 도입된 Fiber는 Reconciliation 엔진을 전면 재설계한 것입니다. 기존 Stack Reconciler는 렌더링 작업을 한 번에 동기적으로 처리했기 때문에, 렌더링이 길어지면 메인 스레드가 블로킹되어 애니메이션이나 사용자 입력이 끊기는 문제가 있었습니다.
Fiber는 렌더링 작업을 잘게 쪼개어(Fiber 단위로) 처리하고, 우선순위가 높은 작업(사용자 입력, 애니메이션)이 들어오면 현재 작업을 잠시 멈추고 먼저 처리한 뒤 재개할 수 있습니다.
┌──────────────────────────────────────────┐
│ Fiber Reconciler │
│ 작업을 Fiber 단위로 분할 │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ F1 │→ │ F2 │→ │ F3 │→ │ F4 │→ ... │
│ └────┘ └────┘ └────┘ └────┘ │
│ 우선순위에 따라 인터럽트·재개 가능 │
└──────────────────────────────────────────┘
Fiber 덕분에 React 18의 Concurrent 기능(Transitions, Suspense 등)이 가능해졌습니다.
Virtual DOM은 빠른 변경 계산을 위해 DOM을 JavaScript 객체로 추상화한 것입니다. Diffing 알고리즘이 최소 변경 사항을 계산하고, Reconciliation이 이를 실제 DOM에 반영합니다. Fiber 아키텍처는 이 과정을 인터럽트 가능한 단위로 쪼개어 UI 반응성을 유지합니다.