티스토리 뷰
React 성능 최적화의 본질: 가상 DOM(Virtual DOM)의 재조정(Reconciliation) 메커니즘과 파이버(Fiber) 아키텍처 심층 분석
1. 브라우저 렌더링 엔진의 한계와 가상 DOM(Virtual DOM)의 탄생 배경
전통적인 웹 애플리케이션 프레임워크는 상태가 변경될 때마다 브라우저의 실제 DOM(Document Object Model)을 직접 조작했습니다. 그러나 현대의 복잡한 단일 페이지 애플리케이션(SPA) 환경에서 자바스크립트를 이용해 실제 DOM을 빈번하게 수정하는 것은 심각한 성능 저하의 주원인이 됩니다. 이는 브라우저가 HTML 코드를 파싱하여 DOM 트리와 CSSOM 트리를 결합하고, 레이아웃을 다시 계산하는 리플로우(Reflow) 과정과 이를 화면에 물리적인 픽셀로 그려내는 리페인트(Repaint) 과정을 수없이 반복하기 때문입니다. 리플로우와 리페인트는 CPU와 GPU 연산 자원을 극도로 소모하는 무거운 작업입니다.
React는 이러한 비용을 최소화하기 위해 메모리상에 가벼운 자바스크립트 객체 형태로 구현된 복사본인 **가상 DOM(Virtual DOM)** 렌더링 패러다임을 도입했습니다. 어떤 상태(State)가 변화하면, React는 실제 화면을 곧바로 갱신하지 않고 메모리 내의 가상 DOM 트리를 먼저 완전히 새로 구축합니다. 그 후 이전의 가상 DOM 트리와 방금 변경된 새로운 가상 DOM 트리를 비교하여, **실제 변경이 일어난 최소한의 노드들만 찾아내어 단 한 번의 패치(Batch Update)로 실제 DOM에 반영**합니다. 이 과정을 통해 브라우저 내부에서 발생하는 무분별한 리플로우 연산을 획기적으로 줄여 연산 효율성을 극대화합니다.
| 비교 대상 | 동작 메커니즘 및 렌더링 파이프라인 | 성능적 장단점 및 특징 |
|---|---|---|
| 실제 DOM (Real DOM) | 변화가 있을 때마다 브라우저 엔진에 직접 명령을 내려 DOM 트리를 수정하고, 전체 화면 아키텍처에 대해 즉각적인 레이아웃 계산(Reflow/Repaint)을 유발함. | 규모가 작은 정적 페이지에서는 직관적이고 빠르나, 대규모 동적 데이터 처리 시 연산 병목 발생. |
| 가상 DOM (Virtual DOM) | 모든 상태 변화를 메모리상의 자바스크립트 객체 구조에서 먼저 일괄 계산하고, 최종 변경 사항의 차이점만 찾아내어 브라우저 전역 DOM에 일괄 바인딩(Batching). | 가상 트리 생성에 따른 추가 메모리 소모가 있으나, 인터랙션이 잦은 대형 웹 앱에서 압도적인 성능 유지. |
2. 재조정(Reconciliation)과 O(N) 서프라이즈: 리액트의 휴리스틱 알고리즘
두 개의 트리 구조를 완전히 비교하여 다른 부분을 찾아내는 일반적인 알고리즘은 **O(N^3)**의 시간 복잡도를 가집니다. 만약 1,000개의 컴포넌트 노드가 있다면 이를 비교하는 데 무려 10억 번의 연산이 필요하다는 뜻이며, 이는 실시간 웹 서비스를 구현하기에 불가능한 수치입니다. 리액트는 이를 해결하기 위해 컴퓨터 공학적 타협점인 두 가지의 **휴리스틱(Heuristic) 가정**을 기반으로 컴포넌트 트리를 동일 레벨에서만 탐색하는 **O(N)** 복잡도의 고속 재조정(Reconciliation) 알고리즘을 설계했습니다.
첫 번째 가정은 **"서로 다른 타입을 가진 두 엘리먼트는 서로 다른 트리를 만들어낼 것"**이라는 점입니다. 예를 들어, 부모 노드의 태그가 <div>에서 <section>으로 변경되면, 리액트는 하위 자식 노드들을 비교하지 않고 이전 트리를 완전히 파괴(Unmount)한 뒤 새로운 트리를 처음부터 완전히 다시 구축(Mount)합니다.
두 번째 가정은 **"개발자가 key 프로퍼티를 제공하면, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않고 유지되어야 하는지 식별할 수 있다"**는 점입니다. Stack Overflow에서 수없이 질문되는 "왜 배열을 렌더링할 때 key 값을 넣어야 하며, 인덱스(index)를 넣으면 왜 안 되는가?"에 대한 정답이 바로 여기에 있습니다. 리액트는 순서가 바뀌거나 중간에 요소가 삽입될 때 고유한 key 값을 기준 삼아 기존 노드를 파괴하지 않고 메모리상에서 그대로 이동(Reorder)시켜 재사용합니다. 고유 키가 없거나 단순 인덱스를 사용하면 배열의 순서가 바뀔 때마다 하위 모든 컴포넌트의 상태가 비정상적으로 덮어씌워지거나 원치 않는 재렌더링 연산이 폭발하게 됩니다.
3. 정밀 제어 예제: 가상 DOM 무력화를 방어하는 메모이제이션(Memoization) API 구현 스펙
리액트의 가상 DOM 연산조차 부모 컴포넌트가 갱신되면 하위 자식 컴포넌트들을 재귀적으로 호출하는 기본 특성을 가집니다. 하위 컴포넌트의 props 변화가 없음에도 불필요한 연산 힙(Heap) 객체가 대량 생성되는 현상을 막기 위해, 실무에서는 React.memo, useMemo, useCallback 삼총사를 활용해 메모리 링킹 구조를 고정해야 합니다.
// 자식 컴포넌트: React.memo를 통해 고차 컴포넌트화(HOC). Props가 변하지 않으면 렌더링 스킵
const OptimizedChild = React.memo(function ChildComponent({ onClickAction, dataPayload }) {
console.log("⚡ 자식 컴포넌트 가상 DOM 연산 가동 (Props 불일치 발견)");
return (
<button onClick={onClickAction} style={{ padding: '10px' }}>
{dataPayload.title} - 클릭 실행
</button>
);
});
export default function ParentDashboard() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// [최적화 1] useCallback으로 부모가 재렌더링되어도 자식에게 전달되는 함수 참조 주소를 동일하게 유지
const handleChildClick = useCallback(() => {
console.log("액션 트리거 처리 완료");
}, []); // 의존성 배열이 비어있으므로 최초 마운트 시의 함수 참조 유지
// [최적화 2] useMemo를 활용하여 객체의 물리적 힙 메모리 참조 주소 오염 방지
const cachedPayload = useMemo(() => {
return { title: "안전한 고밀도 인프라 데이터" };
}, []); // 부모의 상태 'count'나 'text'가 바뀌어도 이 객체는 재생성되지 않음
return (
<div style={{ padding: '20px' }}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={() => setCount(count + 1)}>부모 카운트 업 ({count})</button>
<OptimizedChild onClickAction={handleChildClick} dataPayload={cachedPayload} />
</div>
);
}
4. 동기식 블로킹의 종말: React 18+ 파이버(Fiber) 아키텍처와 비동기식 동시성
리액트 버전 15 이전의 초기 가상 DOM 비교 알고리즘은 브라우저 자바스크립트 스레드를 완전히 점유하는 동기식 Stack 기반 알고리즘이었습니다. 트리가 거대해지면 한 번 비교 연산이 시작되었을 때 연산이 끝날 때까지 렌더링 스레드가 블로킹되어 사용자의 키보드 입력이나 애니메이션이 도중에 툭툭 끊기는 **프레임 드랍(Jank)** 현상이 고질적으로 발생했습니다.
리액트 팀은 이 근본적인 병목을 깨부수기 위해 가상 DOM 재조정 엔진을 완전히 갈아엎은 **리액트 파이버(React Fiber)** 아키텍처를 도입했습니다. 파이버는 가상 DOM 노드 하나하나를 자체적인 작업 가상 프레임 스택 단위를 가지는 객체로 분할합니다. 핵심은 **작업의 '일시 중지', '재개', '우선순위 폐기'가 가능**하다는 점입니다. 대규모 노드 업데이트 도중이라도 사용자의 클릭이나 타이핑 같은 하이 프라이어티(High Priority) 이벤트가 들어오면, 리액트는 진행 중이던 가상 DOM 재조정 작업을 즉시 잠시 멈추고(Yield) 사용자 인터랙션을 먼저 부드럽게 처리한 후, 남은 가상 DOM 연산을 백그라운드 세션에서 이어 나갑니다. 이것이 바로 React 18 동시성(Concurrency) 기능의 본질이자, 현대 프론트엔드가 초고속 최적화를 확보하는 핵심 인프라 기술입니다.
📚 공식 기술 레퍼런스 (References)
- React Reference Documentation: "Advanced Guides - Preserving and Resetting State, Reconciliation Rules" 리액트 코어 명세 참조
- W3C Web Performance Working Group Manual: "HTML Processing Model and Reflow/Repaint Execution Pipeline Specification" 표준 브라우저 파이프라인 명세 인용
- Meta Open Source Engineering Index: "React Fiber Architecture Implementation Architecture and Concurrent Render Scheduling Blueprint" 파이버 동시성 설계도 인용
#리액트가상DOM #ReactVirtualDOM #재조정알고리즘 #Reconciliation #리액트파이버 #ReactFiber #브라우저리플로우 #리액트메모이제이션 #배열key프로퍼티 #동시성렌더링
'Program Development' 카테고리의 다른 글
| gitignore 적용 안됨 해결 방법 (1) | 2026.06.20 |
|---|---|
| 이클립스(Eclipse) 핵심 단축키 모음 (0) | 2026.06.18 |
| 웹 스트리밍(토큰) 데이터 송수신(SSE) (0) | 2026.06.17 |
| VSCode 설치 방법(Windows, MacOS) (0) | 2026.06.11 |
| 컴파일 vs 인터프리터 (1) | 2026.06.10 |
| 메시지 큐(MQ), Kafka, RabbitMQ 아키텍처 내부 원리 (0) | 2026.06.08 |
| 트랜잭션 격리 수준(Isolation Level) 종류와 이상 현상 (0) | 2026.06.06 |
| 프로그래밍 언어별 주석 처리 방법 총정리 (0) | 2026.06.02 |
- Total
- Today
- Yesterday
- C
- Class
- 벡터
- 문자열
- C언어
- 문제풀이
- c#
- 블루투스
- 알고리즘
- 파일처리
- 리스트
- C++ 클래스
- 아두이노
- Java
- DB연동
- 자바
- html
- 정보처리기사
- OpenCV
- MySQL
- 배열
- 클래스
- 자료구조
- C++
- 안드로이드
- String
- Android
- 상속
- 파이썬
- 데이터베이스
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
