Virtual DOM은 React가 UI 업데이트를 효율적으로 처리하기 위해 사용하는 메모리 상의 가상 구조
React는 DOM 조작의 성능 비용이 크다는 문제를 해결하기 위해 Virtual DOM을 도입했다.
Virtual DOM은 상태 변화로 인해 발생하는 UI 업데이트를 메모리 상에서 계산하고, 변경된 부분만 실제 DOM에 반영함으로써 효율적인 UI 업데이트를 제공함
- React는 렌더링이 발생되면 새로운 화면에 들어갈 내용이 담긴 가상 돔을 생성
- 가상 돔은 실제 DOM의 가벼운 복사본으로 메모리 상에 존재하며, JavaScript 객체 형태로 존재
- 리액트는 항상 렌더링 이전의 화면 구조와 렌더링 이후의 화면 구조를 가진 두 개의 가상 돔 객체를 유지
이 두 가상 돔을 비교하여 변경된 부분만 실제 DOM에 반영
(diffing 알고리즘)
diffing 알고리즘 동작 방식
1. 이전 가상돔 트리와 새로운 가상돔 트리를 비교하고 루트 노드에서 시작해서 이전과 새로운 노드를 비교
2. 두 노드가 다른 유형이면 새 노드를 생성하여 기존 노드를 대체
3. 근데 두 노드가 같은 유형이면 속성을 비교해서 변경된 것이 있는지 확인하고 변경된 속성이 없으면 그대로 사용하고 있으면 속성을 업데이트

React는 상태 변경이 여러 번 발생하더라도, 각 상태 변경에 대해 바로 렌더링을 하지 않고 업데이트를 모아 한 번에 처리함.
일반적인 상태 업데이트와 비교
배치 업데이트가 없는 경우: 상태가 변경될 때마다 React는 즉시 Virtual DOM을 업데이트하고, DOM 렌더링도 여러 번 발생합니다.
배치 업데이트가 있는 경우: 여러 상태 변경을 모아서 Virtual DOM과 실제 DOM을 최소한의 렌더링으로 처리합니다.
| 특징 | Reconciliation | Batch Update |
|---|---|---|
| 주요 역할 | Virtual DOM의 변경 사항(diff)을 계산하고 실제 DOM에 반영 | 여러 상태 변경을 그룹화하여 최소한의 렌더링으로 처리 |
| 처리 범위 | Virtual DOM 비교와 실제 DOM 업데이트 상태(state) | 변경을 묶어서 Virtual DOM과 실제 DOM 업데이트 |
| 렌더링 | 최적화 변경된 부분만 DOM에 적용 (diffing 알고리즘) | 불필요한 렌더링 방지 (여러 상태 변경을 하나로 묶어 처리) |
| 적용 시점 | 상태 업데이트 후 Virtual DOM과 실제 DOM 동기화 과정 | 상태 업데이트 발생 시 React가 내부적으로 최적화하여 처리 |
그렇다면 Virtual DOM은 항상 DOM보다 빠른가?
DOM 조작이 작은 간단한 UI의 경우, Virutal DOM이 오히려 오버헤드 될 수 있음.
실시간 데이터 업데이트(애니메이션, 게임 등)는 Virtual DOM보다 명령적 방식이나 Canvas/WebGL 같은 저수준 접근 방식이 더 적합할 수 있다.
선언적 방식에서는 DOM 조작 과정을 프레임워크에 맡기기 때문에, 특정 세부 동작을 미세하게 제어하기 어렵습니다.
React의 추상화가 모든 상황에 최적화되어 있는 것은 아니므로, 특정 상황에서 성능을 최적화하려면 명령적 접근이 필요할 수 있습니다.
개발자 친화적
Virtual DOM은 React가 state에 따라 선언적 방식을 할 수 있게 해줌 즉, 직접 DOM 조작을 개발자가 하지 않아도 됨
// 명령적 방식 예시
// 개발자가 직접 element를 찾아 이벤트를 부여하고
// 새로운 dom 요소 생성 및 추가
<button id="myButton">Click Me</button>
// JavaScript로 이벤트 처리
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
const newElement = document.createElement('div');
newElement.textContent = 'You clicked the button!';
document.body.appendChild(newElement);
});
// 선언적 방식 예시
// 개발자는 무엇(What)을 할지 선언하고
// 과정은 프레임워크에 의해 처리됨
import React, { useState } from 'react';
function App() {
const [clicked, setClicked] = useState(false);
return (
<div>
<button onClick={() => setClicked(true)}>Click Me</button>
{clicked && <div>You clicked the button!</div>}
</div>
);
}
export default App;
| 특징 | 명령적 방식 | 선언적 방식 |
|---|---|---|
| 초점 | "어떻게" DOM을 업데이트할지 설명 | "무엇을" 보여줄지 선언 |
| 코드 복잡성 | 상태 변화에 따라 DOM 조작 코드가 복잡해짐 | 상태를 기반으로 UI를 정의하므로 간결하고 직관적 |
| 유지보수 | DOM 업데이트가 많아질수록 버그 발생 가능성이 높음 | 상태와 UI를 분리하여 유지보수가 쉬움 |