본문으로 건너뛰기

🎨 Virtual DOM이란?

📖 정의

Virtual DOM은 실제 DOM의 가벼운 복사본으로, 메모리에만 존재하는 JavaScript 객체입니다. React 같은 라이브러리는 Virtual DOM을 사용하여 UI 변경사항을 먼저 가상으로 처리한 후, 실제 DOM에 최소한의 변경만 적용합니다. 이를 통해 웹 애플리케이션의 성능을 크게 향상시킵니다.

🎯 비유로 이해하기

건축 설계도 비유

Virtual DOM을 건축 설계도에 비유하면:

실제 건물 (Real DOM)
├─ 건축 비용 비쌈
├─ 수정 시간 오래 걸림
└─ 철거하고 다시 짓기 힘듦

설계도면 (Virtual DOM)
├─ 종이 위에서 자유롭게 수정
├─ 빠르고 저렴
├─ 여러 안을 비교 가능
└─ 최종안만 실제 건물에 반영

과정:
1. 설계도에서 여러 번 수정 (Virtual DOM)
2. 변경된 부분만 파악
3. 실제 건물에 최소한만 적용 (Real DOM)

이렇게 하면 건축 비용과 시간을 절약!

워드 프로세서 비유

메모장 (바닐라 JavaScript)
- 타이핑할 때마다 즉시 저장
- 느리고 비효율적
- 파일 입출력 반복

워드 프로세서 (React + Virtual DOM)
- 메모리에서 편집
- 변경사항 추적
- 저장 버튼 누를 때만 파일에 쓰기
- 빠르고 효율적

Virtual DOM = 메모리 버퍼
Real DOM = 디스크에 저장

게임 렌더링 비유

게임 개발에서:

직접 화면에 그리기 (Real DOM 직접 조작)
- 깜빡임 발생
- 성능 저하
- 부분 업데이트 어려움

더블 버퍼링 (Virtual DOM 방식)
1. 뒷 화면(버퍼)에 그림 그리기
2. 완성되면 앞 화면과 교체
3. 부드러운 렌더링
4. 변경된 픽셀만 업데이트

Virtual DOM이 더블 버퍼링과 같은 역할!

⚙️ 작동 원리

1. DOM이란?

// DOM (Document Object Model)
// HTML을 JavaScript로 조작할 수 있는 인터페이스

<!DOCTYPE html>
<html>
<body>
<div id="root">
<h1>Hello</h1>
<p>World</p>
</div>
</body>
</html>

// DOM 트리
document
└─ html
└─ body
└─ div#root
├─ h1 ("Hello")
└─ p ("World")

// JavaScript로 DOM 조작
document.getElementById('root').innerHTML = '<h1>Changed!</h1>';

2. Real DOM의 문제점

// ❌ 비효율적인 Real DOM 조작

// 문제 1: 매번 전체 리렌더링
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
document.body.appendChild(div); // 1000번 DOM 조작!
}

// 문제 2: 레이아웃 재계산 (Reflow)
element.style.width = '100px'; // Reflow 발생
element.style.height = '100px'; // Reflow 발생
element.style.margin = '10px'; // Reflow 발생

// 문제 3: 복잡한 UI 업데이트
// 한 글자 바뀌어도 전체 컴포넌트 재생성
<div>
<Header />
<Content /> ← 이것만 바뀌었는데
<Footer />
</div>
// 전체를 다시 만들어야 함!

// Real DOM 조작이 느린 이유:
// 1. 브라우저 렌더링 엔진과 JavaScript 엔진 사이 통신
// 2. Reflow (레이아웃 재계산)
// 3. Repaint (화면 다시 그리기)

3. Virtual DOM 동작 과정

// ✅ Virtual DOM의 효율적인 처리

// 1단계: 초기 렌더링
const initialVirtualDOM = {
type: 'div',
props: { id: 'app' },
children: [
{ type: 'h1', props: {}, children: ['Count: 0'] },
{ type: 'button', props: {}, children: ['Click'] }
]
};

// Real DOM에 반영
<div id="app">
<h1>Count: 0</h1>
<button>Click</button>
</div>

// 2단계: 상태 변경 (count = 1)
const newVirtualDOM = {
type: 'div',
props: { id: 'app' },
children: [
{ type: 'h1', props: {}, children: ['Count: 1'] }, // ← 변경됨
{ type: 'button', props: {}, children: ['Click'] }
]
};

// 3단계: Diffing (차이 계산)
const diff = {
path: ['div', 'h1', 'text'],
oldValue: 'Count: 0',
newValue: 'Count: 1'
};

// 4단계: Reconciliation (재조정)
// 최소한의 변경만 Real DOM에 적용
document.querySelector('h1').textContent = 'Count: 1';
// div와 button은 그대로 유지!

4. Diffing 알고리즘

// React의 Diffing 알고리즘 원리

// 규칙 1: 다른 타입의 엘리먼트는 다른 트리 생성
// 이전
<div>
<Counter />
</div>

// 이후
<span>
<Counter />
</span>

// 결과: div를 span으로 완전 교체
// Counter도 언마운트 후 재마운트

// 규칙 2: key를 사용하여 리스트 아이템 추적
// ❌ key 없이
<ul>
<li>A</li>
<li>B</li>
</ul>

// 맨 앞에 C 추가
<ul>
<li>C</li> // 전체를 다시 만듦
<li>A</li>
<li>B</li>
</ul>

// ✅ key 사용
<ul>
<li key="a">A</li>
<li key="b">B</li>
</ul>

// 맨 앞에 C 추가
<ul>
<li key="c">C</li> // C만 추가
<li key="a">A</li> // 그대로
<li key="b">B</li> // 그대로
</ul>

// 규칙 3: 재귀적으로 자식 비교
function diff(oldNode, newNode) {
// 1. 타입이 다르면 교체
if (oldNode.type !== newNode.type) {
return { action: 'REPLACE', newNode };
}

// 2. 속성 비교
const propsDiff = diffProps(oldNode.props, newNode.props);

// 3. 자식 비교 (재귀)
const childrenDiff = diffChildren(
oldNode.children,
newNode.children
);

return { propsDiff, childrenDiff };
}

5. Reconciliation (재조정)

// Reconciliation: Virtual DOM을 Real DOM에 반영

// Phase 1: Render Phase (비동기)
// - Virtual DOM 비교
// - 변경사항 계산
// - 중단 가능 (React 18 Fiber)

// Phase 2: Commit Phase (동기)
// - Real DOM 업데이트
// - 생명주기 메서드 실행
// - 중단 불가능

// 예시: 리스트 업데이트
const oldList = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' }
];

const newList = [
{ id: 1, name: 'A' },
{ id: 3, name: 'C-updated' }, // 수정
{ id: 4, name: 'D' } // 추가
// id: 2 삭제
];

// Reconciliation 결과:
// - id:1 유지
// - id:2 제거
// - id:3 텍스트만 업데이트
// - id:4 추가

// 실제 DOM 조작 최소화:
// remove(id:2)
// updateText(id:3, 'C-updated')
// append(id:4)

💡 실제 예시

기본 Virtual DOM 구현

// 간단한 Virtual DOM 구현 (교육용)

// 1. Virtual DOM 노드 생성
function createElement(type, props = {}, ...children) {
return {
type,
props,
children: children.flat()
};
}

// JSX 없이 사용
const vdom = createElement(
'div',
{ id: 'app', className: 'container' },
createElement('h1', {}, 'Hello Virtual DOM'),
createElement('p', {}, 'This is a paragraph')
);

console.log(vdom);
// {
// type: 'div',
// props: { id: 'app', className: 'container' },
// children: [
// { type: 'h1', props: {}, children: ['Hello Virtual DOM'] },
// { type: 'p', props: {}, children: ['This is a paragraph'] }
// ]
// }

// 2. Virtual DOM을 Real DOM으로 변환
function render(vnode) {
// 텍스트 노드
if (typeof vnode === 'string' || typeof vnode === 'number') {
return document.createTextNode(vnode);
}

// 엘리먼트 노드
const element = document.createElement(vnode.type);

// 속성 적용
Object.entries(vnode.props || {}).forEach(([key, value]) => {
if (key === 'className') {
element.setAttribute('class', value);
} else if (key.startsWith('on')) {
// 이벤트 리스너
const event = key.substring(2).toLowerCase();
element.addEventListener(event, value);
} else {
element.setAttribute(key, value);
}
});

// 자식 렌더링 (재귀)
(vnode.children || []).forEach(child => {
element.appendChild(render(child));
});

return element;
}

// 사용
const domElement = render(vdom);
document.body.appendChild(domElement);

Diffing 알고리즘 구현

// 3. Diffing 알고리즘 (간단 버전)
function diff(oldVNode, newVNode) {
// 1. 새 노드가 없으면 제거
if (!newVNode) {
return { type: 'REMOVE' };
}

// 2. 이전 노드가 없으면 추가
if (!oldVNode) {
return { type: 'CREATE', newVNode };
}

// 3. 타입이 다르면 교체
if (
typeof oldVNode !== typeof newVNode ||
(typeof oldVNode === 'string' && oldVNode !== newVNode) ||
oldVNode.type !== newVNode.type
) {
return { type: 'REPLACE', newVNode };
}

// 4. 같은 타입이면 업데이트
if (newVNode.type) {
return {
type: 'UPDATE',
props: diffProps(oldVNode.props, newVNode.props),
children: diffChildren(oldVNode.children, newVNode.children)
};
}

// 5. 변경 없음
return { type: 'NONE' };
}

// 속성 비교
function diffProps(oldProps = {}, newProps = {}) {
const patches = [];

// 변경되거나 추가된 속성
Object.keys(newProps).forEach(key => {
if (oldProps[key] !== newProps[key]) {
patches.push({ type: 'SET_PROP', key, value: newProps[key] });
}
});

// 제거된 속성
Object.keys(oldProps).forEach(key => {
if (!(key in newProps)) {
patches.push({ type: 'REMOVE_PROP', key });
}
});

return patches;
}

// 자식 비교
function diffChildren(oldChildren = [], newChildren = []) {
const patches = [];
const maxLength = Math.max(oldChildren.length, newChildren.length);

for (let i = 0; i < maxLength; i++) {
patches.push(diff(oldChildren[i], newChildren[i]));
}

return patches;
}

Patch 적용

// 4. Patch를 Real DOM에 적용
function patch(parent, patches, index = 0) {
if (!patches) return;

const element = parent.childNodes[index];

switch (patches.type) {
case 'CREATE':
parent.appendChild(render(patches.newVNode));
break;

case 'REMOVE':
parent.removeChild(element);
break;

case 'REPLACE':
parent.replaceChild(render(patches.newVNode), element);
break;

case 'UPDATE':
// 속성 업데이트
patches.props.forEach(propPatch => {
if (propPatch.type === 'SET_PROP') {
element.setAttribute(propPatch.key, propPatch.value);
} else if (propPatch.type === 'REMOVE_PROP') {
element.removeAttribute(propPatch.key);
}
});

// 자식 업데이트 (재귀)
patches.children.forEach((childPatch, i) => {
patch(element, childPatch, i);
});
break;
}
}

React에서 Virtual DOM 사용

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

// React는 자동으로 Virtual DOM 처리
return (
<div className="counter">
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
);
}

// 내부 동작:
// 1. count 변경 → setState 호출
// 2. React가 새로운 Virtual DOM 생성
// 3. 이전 Virtual DOM과 비교 (Diffing)
// 4. <h1> 텍스트만 변경됨을 감지
// 5. Real DOM의 텍스트 노드만 업데이트
// 6. div, button은 그대로 유지

// 성능 비교:
// ❌ 바닐라 JS: 전체 div.innerHTML 교체
// ✅ React: <h1>의 텍스트만 업데이트

복잡한 리스트 예시

import React, { useState } from 'react';

function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '운동하기', done: false },
{ id: 2, text: '공부하기', done: false },
{ id: 3, text: '청소하기', done: false }
]);

const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};

const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};

return (
<div>
<h1>할 일 목록</h1>
<ul>
{todos.map(todo => (
<li
key={todo.id} // key는 Diffing 최적화에 필수!
style={{
textDecoration: todo.done ? 'line-through' : 'none',
color: todo.done ? '#888' : '#000'
}}
>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>삭제</button>
</li>
))}
</ul>
</div>
);
}

// Virtual DOM의 효율성:
// todo 하나만 체크해도
// - ❌ 전체 리스트 재생성 (X)
// - ✅ 해당 <li>의 style만 변경 (O)

// key의 중요성:
// key가 있으면:
// - 아이템 이동/삭제 시 효율적
// - DOM 재사용 가능
// - 컴포넌트 상태 유지

// key가 없으면:
// - 전체 리스트 재생성
// - 성능 저하
// - 컴포넌트 상태 손실 가능

성능 최적화 예시

import React, { useState, useMemo, memo } from 'react';

// memo로 컴포넌트 메모이제이션
const TodoItem = memo(function TodoItem({ todo, onToggle, onDelete }) {
console.log(`TodoItem ${todo.id} 렌더링`);

return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>삭제</button>
</li>
);
});

function OptimizedTodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '운동하기', done: false },
{ id: 2, text: '공부하기', done: false },
{ id: 3, text: '청소하기', done: false }
]);

// useMemo로 계산 결과 캐싱
const activeTodos = useMemo(() => {
console.log('활성 할일 계산');
return todos.filter(todo => !todo.done);
}, [todos]);

const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};

const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};

return (
<div>
<h1>할 일 목록</h1>
<p>남은 할일: {activeTodos.length}</p>
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</div>
);
}

// 최적화 효과:
// 1. memo: todo 하나만 변경되면 해당 TodoItem만 재렌더링
// 2. useMemo: todos 변경 시에만 activeTodos 재계산
// 3. key: 효율적인 리스트 업데이트

// 성능 비교:
// 최적화 전: 10개 todo 중 1개 변경 → 10개 모두 재렌더링
// 최적화 후: 10개 todo 중 1개 변경 → 1개만 재렌더링

Virtual DOM 없이 vs 있을 때 비교

// ❌ Virtual DOM 없이 (바닐라 JavaScript)
function updateWithoutVirtualDOM() {
const app = document.getElementById('app');
const count = 0;

// 매번 전체 HTML 재생성
setInterval(() => {
count++;
app.innerHTML = `
<div class="container">
<h1>Count: ${count}</h1>
<p>Current time: ${new Date().toLocaleTimeString()}</p>
<button>Click me</button>
</div>
`;
// 문제점:
// 1. 전체 DOM 트리 재생성
// 2. 이벤트 리스너 소실
// 3. 입력 중인 input 초기화
// 4. 스크롤 위치 초기화
// 5. 애니메이션 중단
}, 1000);
}

// ✅ Virtual DOM 있을 때 (React)
function CounterWithVirtualDOM() {
const [count, setCount] = useState(0);
const [time, setTime] = useState(new Date());

useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
setTime(new Date());
}, 1000);

return () => clearInterval(timer);
}, []);

return (
<div className="container">
<h1>Count: {count}</h1>
<p>Current time: {time.toLocaleTimeString()}</p>
<button onClick={() => alert('Clicked!')}>Click me</button>
</div>
);
}

// 장점:
// 1. <h1>과 <p>의 텍스트만 업데이트
// 2. 이벤트 리스너 유지
// 3. 컴포넌트 상태 유지
// 4. 부드러운 업데이트
// 5. 최소한의 DOM 조작

실제 성능 측정

import React, { useState, useEffect } from 'react';

function PerformanceComparison() {
const [items, setItems] = useState([]);
const [renderTime, setRenderTime] = useState(0);

const generateItems = (count) => {
const startTime = performance.now();

const newItems = Array.from({ length: count }, (_, i) => ({
id: i,
value: Math.random()
}));

setItems(newItems);

const endTime = performance.now();
setRenderTime(endTime - startTime);
};

return (
<div>
<h1>성능 비교</h1>
<div>
<button onClick={() => generateItems(100)}>
100개 생성
</button>
<button onClick={() => generateItems(1000)}>
1000개 생성
</button>
<button onClick={() => generateItems(10000)}>
10000개 생성
</button>
</div>

<p>렌더링 시간: {renderTime.toFixed(2)}ms</p>

<div style={{ maxHeight: '400px', overflow: 'auto' }}>
{items.map(item => (
<div key={item.id} style={{ padding: '5px', borderBottom: '1px solid #eee' }}>
Item #{item.id}: {item.value.toFixed(4)}
</div>
))}
</div>
</div>
);
}

// 결과 (일반적인 경우):
// 바닐라 JS (전체 innerHTML):
// - 100개: ~50ms
// - 1000개: ~500ms
// - 10000개: ~5000ms (버벅임)

// React (Virtual DOM):
// - 100개: ~10ms
// - 1000개: ~100ms
// - 10000개: ~1000ms (부드러움)

// Virtual DOM의 성능 향상:
// 초기 렌더링은 비슷하지만,
// 업데이트 시 5-10배 빠름!

크롬 DevTools로 확인하기

// React DevTools Profiler 사용법

// 1. React DevTools 설치
// Chrome 웹 스토어에서 "React Developer Tools" 설치

// 2. Profiler 탭 열기
// - 녹화 버튼 클릭
// - 앱에서 동작 수행
// - 녹화 중지

// 3. 결과 분석
// - Flamegraph: 컴포넌트 렌더링 시간
// - Ranked: 렌더링 시간 순위
// - Component 클릭: 왜 렌더링되었는지 확인

function ProfiledComponent() {
const [count, setCount] = useState(0);

// Profiler로 감싸서 렌더링 성능 측정
return (
<React.Profiler
id="Counter"
onRender={(id, phase, actualDuration) => {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}}
>
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
</React.Profiler>
);
}

// Console 출력:
// Counter (mount) took 2.3ms
// Counter (update) took 0.5ms ← Virtual DOM 덕분에 빠름!

🤔 자주 묻는 질문

Q1. Virtual DOM이 항상 Real DOM보다 빠른가요?

A: 아니요, 상황에 따라 다릅니다:

// ✅ Virtual DOM이 빠른 경우:
// 1. 복잡한 UI 업데이트
function ComplexUI() {
const [data, setData] = useState([...]);

// 많은 컴포넌트 중 일부만 변경
return (
<div>
{data.map(item => <ComplexComponent key={item.id} {...item} />)}
</div>
);
// Virtual DOM: 변경된 부분만 업데이트
// Real DOM: 전체 재생성 필요
}

// 2. 빈번한 업데이트
function LiveCounter() {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 100); // 초당 10번 업데이트
return () => clearInterval(timer);
}, []);

return <h1>Count: {count}</h1>;
// Virtual DOM: 텍스트만 변경
// Real DOM: 매번 DOM 조작
}

// ❌ Virtual DOM이 느린 경우:
// 1. 간단한 UI (오버헤드)
function SimpleButton() {
return <button>Click</button>;
}
// Virtual DOM 비교 + Real DOM 업데이트
// vs
// Real DOM 직접 조작

// 2. 초기 렌더링
// Virtual DOM 생성 + Diffing + Real DOM 생성
// vs
// Real DOM 직접 생성

// 결론:
// Virtual DOM의 장점은 "업데이트 성능"
// 초기 로딩은 약간 느릴 수 있지만,
// 복잡한 앱에서는 전체적으로 빠름!

Q2. key는 왜 중요한가요?

A: key는 React가 어떤 아이템이 변경/추가/삭제되었는지 식별하는 데 사용됩니다:

// ❌ key 없이 (비효율적)
function BadList() {
const [items, setItems] = useState(['A', 'B', 'C']);

return (
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
);
}

// 'D'를 맨 앞에 추가하면:
// 이전: <li>A</li> <li>B</li> <li>C</li>
// 이후: <li>D</li> <li>A</li> <li>B</li> <li>C</li>

// React는 key가 없으면:
// - 0번째: A → D (변경)
// - 1번째: B → A (변경)
// - 2번째: C → B (변경)
// - 3번째: 없음 → C (추가)
// 결과: 4개 모두 업데이트! (비효율)

// ✅ key 사용 (효율적)
function GoodList() {
const [items, setItems] = useState([
{ id: 'a', text: 'A' },
{ id: 'b', text: 'B' },
{ id: 'c', text: 'C' }
]);

return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}

// 'D'를 맨 앞에 추가하면:
// 이전: key=a, key=b, key=c
// 이후: key=d, key=a, key=b, key=c

// React는 key로 식별:
// - key=a: 그대로 (유지)
// - key=b: 그대로 (유지)
// - key=c: 그대로 (유지)
// - key=d: 새로 추가
// 결과: 1개만 추가! (효율)

// key 규칙:
// 1. 형제 사이에서 고유해야 함
// 2. 변하지 않아야 함
// 3. 예측 가능해야 함

// ❌ 나쁜 key 예시:
{items.map((item, index) => <li key={index}>{item}</li>)}
// 인덱스는 순서가 바뀌면 문제 발생!

{items.map(item => <li key={Math.random()}>{item}</li>)}
// 매번 새로운 key → 항상 재생성!

// ✅ 좋은 key 예시:
{items.map(item => <li key={item.id}>{item}</li>)}
// 데이터의 고유 ID 사용

{items.map(item => <li key={item.email}>{item}</li>)}
// 고유한 속성 사용

Q3. Virtual DOM을 직접 조작할 수 있나요?

A: 아니요, React는 자동으로 처리합니다. 하지만 최적화는 가능합니다:

import React, { useState, useMemo, useCallback, memo } from 'react';

// 1. React.memo - 컴포넌트 메모이제이션
const ExpensiveComponent = memo(function ExpensiveComponent({ value }) {
console.log('ExpensiveComponent 렌더링');

// 복잡한 계산
const result = useMemo(() => {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum + value;
}, [value]);

return <div>Result: {result}</div>;
});

// props가 같으면 재렌더링 스킵!

// 2. useMemo - 값 메모이제이션
function ListComponent({ items }) {
// items가 변경될 때만 재계산
const sortedItems = useMemo(() => {
console.log('정렬 중...');
return [...items].sort((a, b) => a.value - b.value);
}, [items]);

return (
<ul>
{sortedItems.map(item => (
<li key={item.id}>{item.value}</li>
))}
</ul>
);
}

// 3. useCallback - 함수 메모이제이션
function Parent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);

// count가 변경될 때만 새 함수 생성
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);

return (
<div>
<Child onClick={handleClick} />
<button onClick={() => setOther(other + 1)}>
Other: {other}
</button>
</div>
);
}

const Child = memo(function Child({ onClick }) {
console.log('Child 렌더링');
return <button onClick={onClick}>Click</button>;
});

// other 변경 시 Child는 재렌더링 안 됨!

// 4. shouldComponentUpdate / PureComponent (클래스형)
class OptimizedComponent extends React.PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}

// props가 변경되지 않으면 재렌더링 안 함

// 5. 조건부 렌더링 최적화
function ConditionalRendering({ showDetails, basicInfo, detailInfo }) {
return (
<div>
<BasicInfo data={basicInfo} />
{showDetails && <DetailInfo data={detailInfo} />}
</div>
);
}

// showDetails가 false면 DetailInfo는 Virtual DOM에도 없음!

Q4. Virtual DOM 없는 프레임워크도 있나요?

A: 네, Svelte 같은 프레임워크는 다른 방식을 사용합니다:

// React (Virtual DOM 사용)
function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}

// 런타임에:
// 1. Virtual DOM 생성
// 2. Diffing
// 3. Real DOM 업데이트

// Svelte (컴파일 타임 최적화)
<script>
let count = 0;
</script>

<div>
<h1>Count: {count}</h1>
<button on:click={() => count += 1}>+</button>
</div>

// 컴파일 결과:
function update_count() {
h1.textContent = `Count: ${count}`; // 직접 DOM 조작!
}

// 장단점 비교:

// Virtual DOM (React, Vue)
// 장점:
// - 선언적 UI
// - 예측 가능한 성능
// - 풍부한 생태계

// 단점:
// - 메모리 오버헤드
// - Diffing 비용
// - 번들 크기 큼

// No Virtual DOM (Svelte)
// 장점:
// - 빠른 성능
// - 작은 번들 크기
// - 낮은 메모리 사용

// 단점:
// - 생태계 작음
// - 복잡한 앱에서는?
// - 학습 곡선

// Solid.js (Fine-grained Reactivity)
// Virtual DOM 없이 React 문법 사용
import { createSignal } from 'solid-js';

function Counter() {
const [count, setCount] = createSignal(0);

return (
<div>
<h1>Count: {count()}</h1>
<button onClick={() => setCount(count() + 1)}>+</button>
</div>
);
}

// 변경된 부분만 정확히 업데이트!
// Virtual DOM 없이도 효율적!

Q5. React 18의 Concurrent Rendering은 무엇인가요?

A: Virtual DOM 업데이트를 중단/재개할 수 있게 하는 기능입니다:

import React, { useState, useTransition, Suspense } from 'react';

// 1. useTransition - 낮은 우선순위 업데이트
function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();

const handleChange = (e) => {
// 즉시 업데이트 (높은 우선순위)
setQuery(e.target.value);

// 나중에 업데이트 (낮은 우선순위)
startTransition(() => {
// 무거운 계산
const newResults = expensiveSearch(e.target.value);
setResults(newResults);
});
};

return (
<div>
<input
value={query}
onChange={handleChange}
placeholder="검색..."
/>
{isPending && <span>검색 중...</span>}
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
</div>
);
}

// 동작:
// 1. 사용자가 빠르게 타이핑
// 2. query는 즉시 업데이트 (input 반응성 유지)
// 3. results는 천천히 업데이트 (백그라운드)
// 4. 새 타이핑이 들어오면 이전 results 업데이트 중단!

// 2. Suspense - 로딩 상태 관리
const LazyComponent = React.lazy(() => import('./Heavy'));

function App() {
return (
<Suspense fallback={<div>로딩 중...</div>}>
<LazyComponent />
</Suspense>
);
}

// 3. useDeferredValue - 값 지연
function FilteredList({ items, query }) {
const deferredQuery = useDeferredValue(query);

const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.includes(deferredQuery)
);
}, [items, deferredQuery]);

return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}

// Concurrent Rendering의 장점:
// 1. 응답성 유지: UI가 버벅이지 않음
// 2. 우선순위: 중요한 업데이트 먼저
// 3. 중단 가능: 불필요한 작업 스킵
// 4. 부드러운 전환: 갑작스러운 변화 방지

// 내부 동작:
// Time Slicing:
// [타이핑] [렌더링] [타이핑] [렌더링] ...
// 작업을 여러 작은 조각으로 나누어 실행

// 우선순위:
// Urgent (즉시): 클릭, 타이핑, 호버
// Transition (나중에): 검색 결과, 필터링
// Deferred (가장 나중에): 분석, 로깅

🎓 다음 단계

Virtual DOM을 이해했다면, 다음을 학습해보세요:

  1. React란? (문서 작성 예정) - Virtual DOM을 사용하는 대표 라이브러리
  2. CSR vs SSR vs SSG - 렌더링 전략 비교
  3. 웹 성능 최적화 (문서 작성 예정) - Virtual DOM 활용한 최적화

🎬 마무리

Virtual DOM은 현대 웹 프레임워크의 핵심 기술입니다:

  • 개념: Real DOM의 가벼운 복사본
  • 원리: Diffing으로 변경사항 계산 후 최소 업데이트
  • 장점: 복잡한 UI 업데이트 성능 향상
  • 핵심: 선언적 프로그래밍으로 개발자 경험 개선

Virtual DOM 덕분에 우리는 성능 걱정 없이 UI 로직에 집중할 수 있습니다!