개발/프로젝트

크래프톤 정글 × 수요코딩회"React를 구현하라"

cedis 2026. 3. 26. 10:18

 

크래프톤 정글 × 수요코딩회

"React를 구현하라" — Virtual DOM과 Diff 알고리즘을
직접 구현한 3인 팀 프로젝트 회고

React를 사용할 때마다 궁금했다.
"Virtual DOM은 실제로 무엇을, 어떻게 비교할까?"

👥 3인 팀 🌐 Vanilla JS Only ⚛️ Virtual DOM Engine 📊 Benchmark System

React를 사용할 때마다 한 가지가 늘 궁금했다.
"Virtual DOM이 빠르다고 하는데, 실제로는 어떤 방식으로 비교가 일어날까?"

크래프톤 정글의 수요코딩회에서 React의 핵심 개념인 Virtual DOM과 Diff 알고리즘을 직접 구현하는 과제가 주어졌다. 외부 라이브러리 없이 HTML·CSS·Vanilla JavaScript만으로, React가 내부에서 수행하는 일을 우리 손으로 다시 만들어보는 프로젝트였다.

이 글은 그 과정에서 내가 맡아 설계하고 구현한 부분, 팀원들과 병합하며 정리한 역할 분담, 그리고 직접 구현하면서 비로소 선명해진 브라우저 렌더링 구조를 기록한 회고다.

· · ·

시작은 크래프톤 정글 수요코딩회

단 하루, AI를 도구 삼아 팀별 프로젝트에 몰입한다. 결과물뿐만 아니라 과정도 중요하다.

크래프톤 정글의 수요코딩회는 매주 수요일 진행되는 하루 몰입형 팀 프로젝트다. 이번 과제의 키워드는 딱 하나였다.

"React의 핵심 개념인
Virtual DOM과 Diff 알고리즘을 구현하라."

단순히 코드만 동작하면 되는 게 아니라, AI가 작성한 코드를 완벽히 소화해 그 원리를 설명할 수 있어야 한다. 낯선 기능도 Top-down으로 빠르게 풀어내며 '학습 민첩성'을 극대화하는 것이 목표였다.

📋 과제 요구사항

1

DOM → Virtual DOM 변환

브라우저의 실제 DOM 정보를 읽어서 Virtual DOM 트리로 변환하는 함수 작성

2

Diff 알고리즘 구현

두 Virtual DOM을 비교하여 변경된 부분을 찾아내는 알고리즘 작성

3

Patch 적용

변경된 부분만 "실제 영역"에 렌더링 — Patch, 뒤로가기, 앞으로가기 버튼 포함

4

State History

VDOM history에 저장하여 Undo/Redo 지원, 실제 영역과 테스트 영역 동기화

팀 프로젝트: pre-Fiber React 엔진 + Demo Lab + Benchmark

팀은 3명이었고, 각자 독립 브랜치에서 구현한 뒤 최종 결과물을 병합하는 전략을 택했다. 그 과정에서 내 브랜치(choihyunjin, choihyunjin2)에서는 리액트 엔진 코어와 벤치마크 시스템을 중심으로 설계와 구현을 맡았다.

PRESENTATION
Demo Lab
실제영역 / 테스트영역
Patch · Undo · Redo
CORE ENGINE
team3-react
VDOM · Diff · Patch
Render · Traverse
VERIFICATION
Benchmark
VDOM vs RDOM
성능 비교 측정

📁 프로젝트 구조

📦 packages/team3-react/src/index.js — 엔진 진입점 ⚙️ core/vdom.js — VDOM 핵심 로직 🧪 demo-lab/ — 시연 페이지 📊 benchmark/ — 성능 측정 🏠 index.html — 대시보드

팀원별 역할 분담 & 나의 기여

3명이 각자 독립 브랜치에서 구현한 뒤 최종 병합하는 전략을 택했다.
나는 프로젝트의 초기 구조와 엔진 코어, 벤치마크 시스템을 맡았고, 팀원들은 Diff Lab 구현과 문서화, 마무리 정리를 담당했다.

팀원 브랜치 핵심 담당
최현진 (나) choihyunjin, choihyunjin2 🔵 프로젝트 기반 구축 · 리액트 엔진 코어 · 벤치마크 시스템 · UI/시각화 설계 및 구현
최영빈 choiyeongbeen 🟢 Diff Lab 구현 · 기술 매뉴얼/스펙 문서 작성 · README 정비
정찬빈 jungchanbin 🟡 코드 수정 · README 업데이트

🔵 내 기여 — 커밋 타임라인

프로젝트의 첫 커밋부터 최종 벤치마크 안정화까지, 내가 맡은 설계·구현 흐름을 시간순으로 정리했다.

1

Add Virtual DOM service demo — 03/24 10:32

프로젝트 최초 커밋. 전체 프로젝트 구조 수립, VDOM 서비스 데모 기반을 만듦. 이 커밋 위에 모든 팀원의 작업이 올라감.

2

Add admin validation drawer — 03/24 11:02

관리자 검증 기능 추가. VDOM 상태를 검증하고 디버깅할 수 있는 관리자 패널 구현.

3

feat: UI redesign + Virtual DOM Tree enhanced visualization — 03/25 00:56

UI 전면 리디자인. Virtual DOM 트리를 시각적으로 탐색할 수 있는 강화된 시각화 시스템 구축.

4

Refine benchmark safety and controls — 03/25 03:55

벤치마크 시스템 안전장치 및 컨트롤 개선. VDOM vs RDOM 성능 비교 측정 체계 완성.

5

feat: Add 20s auto-stop & expandable Tree UI cutoff — 03/25 05:44

벤치마크 20초 자동 정지 기능 추가. 대형 트리에서도 안전하게 동작하도록 확장/축소 가능한 트리 UI 구현.

🎯 내가 직접 설계하고 구현한 핵심 기능

① 프로젝트 기반 구축

프로젝트 최초 커밋으로 전체 디렉토리 구조, 모듈 분리, VDOM 서비스 데모의 뼈대를 만들었다. 이 기반 위에 모든 팀원의 작업이 올라갔다.

② 리액트 엔진 코어 (4계층 파이프라인)

VDOM 생성 → 정규화 → 비교(Diff) → 적용(Patch)까지 이어지는 엔진 파이프라인 전체를 설계. core/vdom.jspackages/team3-react/src/index.js의 핵심 로직.

③ 벤치마크 시스템

VDOM 생성·DOM walk·diff+patch 시간을 각각 측정하는 벤치마크 시스템 전체를 설계·구현. 안전장치(20초 자동 정지)까지 포함.

④ UI / VDOM 트리 시각화

Virtual DOM 트리 구조를 시각적으로 탐색할 수 있는 인터랙티브 시각화 시스템. 확장/축소 가능한 트리 UI + 전체 페이지 UI 리디자인.

⑤ 관리자 검증 기능 (Validation Drawer)

VDOM 상태를 검증하고 디버깅할 수 있는 관리자 패널. 내부 상태 확인과 엣지 케이스 검증에 활용.

📊 팀원별 담당 영역 한눈에 보기

아래 영역은 기여도의 크기 비교가 아니라 각 팀원이 맡은 역할을 동일한 비중으로 정리한 섹션이다. 팀 프로젝트인 만큼 막대 길이는 동일하게 두고, 담당한 범주만 다르게 표기했다.

최현진 (나) — 엔진 코어 · 벤치마크 · UI · 기반 구축
🏗️ 기반구축   ⚙️ 엔진코어   📊 벤치마크   🎨 UI/시각화   🔧 검증기능
최영빈 — Diff Lab · 문서화 · README
🧪 Diff Lab   📝 문서/스펙
정찬빈 — 코드 수정 · README
📄 README
💡 핵심 포인트

이 프로젝트에서 나는 초기 구조를 세우고, 그 위에 리액트 엔진 코어와 벤치마크 시스템을 설계·구현했다. 단순히 기능을 추가하는 데서 끝나지 않고, 팀원들이 각자 작업한 결과물을 자연스럽게 합칠 수 있도록 프로젝트 구조와 인터페이스를 먼저 정리한 점이 특히 의미 있었다.

내가 만든 것: React 엔진 — 4계층 파이프라인

— 단순히 "Diff만 구현했다"가 아니라, DOM → VDOM 생성 → 정규화 → 비교 → 패치 적용까지
하나의 엔진 파이프라인으로 설계했다.

핵심 구조: 엔진 파이프라인
핵심 구조 — 입력 HTML/실제 DOM에서 최종 DOM 반영까지의 계층 구조
1. VDOM 생성 계층
  • domNodeToVNode
  • domToVNode
  • sourceToVNode
  • createElementVNode
  • createTextVNode
  • createRootContainer
  • normalizeVNodePaths
2. 렌더 / 탐색 계층
  • createDOMFromVNode
  • renderVNodeToRoot
  • findVNodeByPath
  • traverseDFS
  • traverseBFS
3. 비교 계층 (Diff)
  • diffAttrs
  • diffChildren
  • createChildKeyMap
  • diff
  • diffRoot
4. 적용 계층 (Patch)
  • getDomNodeByPath
  • applyCreate / Remove / Replace / Text
  • applyAttrSet / Remove
  • applyReorderPatch
  • applyPatches
  • patchRoot
함수 그룹 지도
함수 그룹 지도 — 각 계층의 함수들이 어떻게 연결되는지 보여주는 의존성 맵

🌳 Virtual DOM 노드 구조

HTML DOM을 JS 객체로 표현한 가벼운 트리 구조. 각 노드는 아래 필드를 가진다.

// VDOM 노드 구조 { type: "element" | "text", tag: "div" | "article" | "button" | ..., attrs: { id: "app", class: "main" }, children: [ /* child VNodes */ ], text: "Hello World", key: "unique-key", path: "0.1.2", // patch 적용 위치 추적용 depth: 2 // 트리 깊이 }

🔧 Patch 타입 — Diff 결과물

Diff 알고리즘이 두 VDOM을 비교한 결과로 생성하는 변경 명세서.

CREATE REMOVE REPLACE TEXT ATTR_SET ATTR_REMOVE REORDER_CHILDREN

왜 Virtual DOM이 필요한가 — 브라우저 렌더링의 비용

프로젝트를 하면서 가장 깊게 파고든 질문이었다.
"리액트는 싸고 브라우저 렌더링 엔진은 비싸다" — 이 말의 진짜 의미는 무엇인가.

🌐 브라우저 렌더링 파이프라인

HTML → DOM Tree
HTML을 파싱해 트리 구조로 변환
CSS → CSSOM
스타일 규칙 파싱 및 생성
Render Tree 생성
DOM + CSSOM 결합 → 화면에 보일 것만 추출
Layout (Reflow) — CPU
각 요소의 위치·크기 계산 — 가장 비쌈
Paint — CPU/GPU
색, 글자, 테두리, 그림자 등 픽셀로 그리기
Composite — GPU
레이어 합성 → 최종 화면 출력
구분 React 엔진 (Virtual DOM) 브라우저 렌더링 엔진
역할 변경점 계산 (어디를 바꿀지 결정) 실제 화면 생성 및 그리기
실행 위치 JavaScript 엔진 (V8) 브라우저 렌더링 파이프라인
CPU 사용 ✔️ 매우 많이 사용 ✔️ 많이 사용
GPU 사용 ❌ 사용 안 함 ✔️ Composite 등에서 사용
데이터 메모리 내부 JS 객체 (Virtual DOM) 실제 DOM + CSSOM + Render Tree
연산 성격 순수 계산 (논리/비교) 계산 + 그래픽 처리
비용 비교적 가벼움 매우 비쌈 (Layout + Paint + GPU)
최적화 목표 불필요한 렌더링 줄이기 Reflow / Repaint 최소화

⚡ 전체 흐름 — React가 끼어드는 위치

React (CPU)
최소 DOM 변경
브라우저 (CPU)
Composite (GPU)

Virtual DOM diff → 변경점 계산 → Style 계산 → Layout → Paint → Composite

💡 핵심 인사이트

리액트의 Virtual DOM 비교는 자바스크립트 메모리 상에서 일어나는 CPU 연산이라 비교적 가볍다. 반면 실제 DOM 변경은 브라우저의 스타일 계산, 레이아웃 재계산, 페인트 같은 후속 렌더링 비용을 유발할 수 있어서 더 비싸다. 그래서 리액트는 먼저 싼 계산으로 변경점을 찾고, 비싼 실제 렌더링 작업은 최소화하려는 전략이다.

🔥 Reflow / Repaint / Composite — 비용 차이

단계 의미 비용 CPU/GPU 트리거 예시
Layout (Reflow) 요소의 크기/위치 재계산 ❌ 가장 비쌈 CPU width, height, margin
Paint (Repaint) 픽셀 다시 그리기 ⚠️ 중간 CPU/GPU color, background
Composite 레이어 합성 ✔️ 비교적 저렴 GPU transform, opacity

Layout은 "각 요소를 어디에, 얼마나 크게 놓을까"를 계산하는 설계도 단계다. 하나가 바뀌면 부모·형제·자식까지 연쇄적으로 재계산이 퍼질 수 있어서 가장 비싸다.
Paint는 그 설계도를 바탕으로 실제 색, 글자, 그림자를 칠하는 단계다.
Composite는 이미 그려진 레이어들을 합치는 단계로, GPU가 병렬 처리하므로 상대적으로 가볍다.

Diff 알고리즘 — 두 트리의 차이를 찾는 핵심

이전 Virtual DOM과 새로운 Virtual DOM을 비교해서, 무엇이 바뀌었는지 찾아내는 과정.

🔍 Diff → Patch → DOM 반영 흐름

이전 VDOM
+
새 VDOM
Diff 알고리즘
Patch 목록 생성
실제 DOM에 최소 반영

🎯 Diff의 5가지 핵심 케이스

케이스 조건 결과 Patch
① 노드 추가 이전엔 없고, 새로 생김 CREATE
② 노드 삭제 이전엔 있었고, 사라짐 REMOVE
③ 태그 교체 태그 이름 자체가 다름 (p→div) REPLACE
④ 텍스트/속성 변경 같은 태그, 내용만 다름 TEXT / ATTR_SET
⑤ 자식 재정렬 key 기반으로 순서/추가/삭제 감지 REORDER_CHILDREN

🔑 key가 왜 중요한가

리스트에서 단순 인덱스 비교만 하면 "위치만 바뀐 것"과 "내용이 바뀐 것"을 구분할 수 없다. key를 통해 노드를 식별하면, 추가·삭제·이동을 정확하게 판단할 수 있다.

// key가 없으면 — 전부 "변경"으로 판단 <li>A</li><li>B</li> // "A→B 변경" <li>B</li><li>A</li> // "B→A 변경" // key가 있으면 — "이동"으로 정확히 판단 <li key="1">A</li><li key="2">B</li> <li key="2">B</li><li key="1">A</li> // → REORDER_CHILDREN (이동만 하면 됨)

구현에서는 data-key, key, id 속성 순으로 key를 추출하고, createChildKeyMap으로 Map(hashtable) 기반 O(1) 조회를 지원했다.

💡 핵심 인사이트

일반적인 트리 비교는 O(n³)이지만, React 방식은 "같은 레벨끼리만 비교 + key로 리스트 비교"라는 규칙을 적용해 O(n)으로 최적화한다. 직접 구현해보니 이 규칙이 왜 필요한지, 왜 이 정도 타협이 합리적인지를 코드로 이해하게 됐다.

가장 어려웠던 기술적 챌린지 3가지

구현하면서 "이건 그냥 넘길 수 없다"고 느꼈던 순간들

CHALLENGE 01

🌐 실제 DOM → VDOM 변환 — "살아있는 구조"를 "죽은 객체"로 바꾸기

브라우저의 실제 DOM은 단순한 데이터가 아니라 이벤트 리스너, 스타일 계산, 자식 노드 관계 등이 얽힌 "살아있는 자료구조"다. 이걸 순수한 JS 객체 트리로 변환하는 것이 첫 번째 관문이었다.

// 실제 DOM 노드를 VDOM 객체로 변환 function domNodeToVNode(domNode) { if (domNode.nodeType === Node.TEXT_NODE) { return createTextVNode(domNode.textContent); } const attrs = {}; for (const attr of domNode.attributes) { attrs[attr.name] = attr.value; } const children = Array.from(domNode.childNodes) .map(child => domNodeToVNode(child)); return createElementVNode(domNode.tagName, attrs, children); }

변환 후에는 normalizeVNodePaths로 각 노드에 pathdepth를 부여해야 했다. 이 정규화 과정이 없으면 나중에 Patch를 적용할 때 "어떤 DOM 노드에 적용할지" 찾을 수 없기 때문이다.

💡 핵심 인사이트

DOM은 화면과 연결된 "살아있는 자료구조"라서 값 하나 바꾸는 것도 비싸다. Virtual DOM은 화면과 연결이 끊긴 "순수 JS 객체"라서 비교·복사·수정이 자유롭다. 이 차이를 직접 구현하면서 체감했다.

CHALLENGE 02

⚙️ Diff 알고리즘 — diffChildren이 가장 까다로웠다

단일 노드 비교(diff)는 비교적 단순하다. 태그 다르면 교체, 텍스트 다르면 수정. 하지만 자식 노드 리스트 비교(diffChildren)는 완전히 다른 문제였다.

단순 인덱스 비교로는 "추가·삭제·이동"을 구분할 수 없다. key 기반으로 createChildKeyMap을 만들어 Map(hashtable) 조회를 쓰고, 기존 노드를 재사용할 수 있는 경우와 새로 만들어야 하는 경우를 분리하는 로직이 필요했다.

💡 핵심 인사이트

React가 key를 요구하는 이유를 이때 비로소 이해했다. key 없이는 diff 알고리즘이 "이동"을 감지하지 못하고 전부 "삭제 후 재생성"으로 처리해야 하는데, 이건 성능적으로 Virtual DOM의 이점을 완전히 날려버리는 것이었다.

CHALLENGE 03

📊 벤치마크 시스템 — "진짜 빠른지" 증명하기

"Virtual DOM이 빠르다"는 이론을 검증하기 위해 벤치마크 시스템을 직접 만들었다. VDOM 생성 시간, DOM walk 시간, diff + patch를 통한 DOM 업데이트 시간을 각각 측정했다.

VDOM diff + patch 빠름
최소 변경
Real DOM 전체 재렌더링 느림
전체 재생성

벤치마크 결과, 변경이 적은 경우 VDOM 방식이 압도적으로 유리했다. I/O 없이 메모리 안에서 비교하는 것과, 실제 DOM을 통째로 재생성하는 것의 차이는 직접 수치로 보니 확연했다.

💡 핵심 인사이트

"Virtual DOM이 빠르다"는 무조건이 아니다. 변경이 거의 없을 때, 복잡한 트리에서 일부만 바뀔 때 유리하다. 정말 작은 화면에서 변경이 전체적으로 일어나면 오히려 diff 오버헤드가 추가될 수도 있다. 이걸 벤치마크로 직접 확인한 것이 큰 수확이었다.

Virtual DOM만이 답은 아니다 — 경쟁 업데이트 방식 비교

프로젝트를 통해 "React 방식이 유일한 정답인가?"라는 질문까지 확장하게 됐다.

방식 대표 변경 감지 Diff 필요? 특징
Virtual DOM React 전체 트리 비교 ✔️ 있음 안정적, 범용
Fine-grained Reactivity SolidJS 값 단위 직접 추적 ❌ 없음 매우 빠름
Compile-time Svelte 컴파일 시 분석 ❌ 없음 번들 작음, 매우 빠름
Signals Angular (최신) 상태 기반 자동 추적 ❌ 없음 직관적, 구조화
Incremental DOM Google 직접 DOM 비교+수정 ❌ 없음 메모리 적음

🎯 핵심 차이 — "비교해서 찾느냐" vs "처음부터 추적하느냐"

⚛️ React 방식

state 변경 → 컴포넌트 재실행 → Virtual DOM 전체 생성 → 이전과 비교 → 바뀐 부분만 DOM 업데이트

"다시 그리고 → 걸러냄"

🎯 Solid/Svelte 방식

count 변경 → count를 사용하는 DOM만 바로 업데이트. 컴포넌트 재실행 없음. diff 없음.

"처음부터 필요한 것만 실행"

💡 핵심 인사이트

결과는 비슷하지만 과정(알고리즘 구조)이 완전히 다르다. React는 "트리 비교 알고리즘 문제"이고, Solid/Svelte는 "의존성 그래프 추적 문제"다. 결국 둘 다 목표는 같다 — 브라우저의 비싼 작업을 최소화하자. 차이는 "어디서 바뀐 걸 알아내느냐"에 있다.

Demo Lab — 직접 눈으로 확인하는 VDOM → Diff → Patch

구현한 엔진을 시각적으로 검증할 수 있는 데모 페이지.

🖥️ Demo Lab 동작 흐름

1

페이지 로드

"실제 영역"의 샘플 HTML을 Virtual DOM 트리로 변환 → 테스트 영역에 렌더링

2

사용자 수정

"테스트 영역"에서 HTML을 자유롭게 수정 (태그 추가, 텍스트 변경, 속성 수정 등)

3

Patch 버튼 클릭

수정된 상태 → 새 VDOM 생성 → 이전 VDOM과 Diff → 변경분만 "실제 영역"에 반영

4

State History 저장

변경된 VDOM이 history에 저장됨 → 뒤로가기/앞으로가기로 특정 시점 복원 가능

🔍 Demo Lab에서 확인할 수 있는 것들

HTML → VDOM 변환 결과 VDOM 트리 시각화 Diff 비교 후 Patch 목록 실제 DOM 최소 변경 적용 Undo / Redo (History) 다양한 엣지 케이스 테스트

팀 병합 — 5개 브랜치를 하나로

3명이 각자 다른 방향으로 구현한 뒤 DEVELOPE 브랜치로 병합하는 과정은 생각보다 섬세한 정리가 필요했다.

🔀 Merge Strategy

기준 선정

각 브랜치의 핵심 기여물을 먼저 파악하고, 중복 구현은 가장 완성도 높은 것 기준으로 통합

엔진 코어 기반 — choihyunjin + choihyunjin2

내 브랜치에서 만든 리액트 엔진 코어(4계층 파이프라인) + 벤치마크 시스템 + UI/시각화가 최종 결과물의 뼈대가 됨

Diff Lab + 문서 통합 — choiyeongbeen

최영빈의 Diff Lab 구현과 기술 매뉴얼/스펙 문서가 엔진 위에 통합됨

README + 최종 정리 — jungchanbin

정찬빈의 README 업데이트와 최종 마무리 작업이 합쳐져 DEVELOPE 브랜치로 완성

💡 병합 과정에서 배운 점

초기에 프로젝트 구조와 엔진 인터페이스를 먼저 정리해 둔 덕분에, 팀원들이 각자의 브랜치에서 작업한 결과물을 하나의 흐름으로 묶어내기가 훨씬 수월했다. 이번 병합 과정은 좋은 기반 설계가 결국 팀 전체의 병합 비용을 줄인다는 사실을 체감하게 해 준 경험이었다.

배운 것들 — 직접 만들어야만 보이는 것들

LESSON 01

DOM은 "살아있는 자료구조"다

JS 객체의 값 하나 바꾸는 건 싸지만, DOM의 텍스트 하나 바꾸는 건 Layout·Paint·Composite를 유발할 수 있다. 직접 변환 함수를 짜면서 이 차이를 체감했다.

LESSON 02

O(n) Diff가 당연하지 않다

일반 트리 비교는 O(n³). "같은 레벨끼리만 비교 + key로 식별"이라는 규칙이 있어야 O(n)이 되는 것이고, 이 규칙은 "완벽한 비교를 포기하는 대신 실용적 속도를 택하는 타협"이다.

LESSON 03

React가 key를 요구하는 이유

key 없이 diffChildren을 구현해보면 "이동"과 "변경"을 구분할 수 없다. key가 있으면 Map 기반 O(1) 조회로 정확한 판단이 가능해진다. 직접 겪어야 이해되는 부분이었다.

LESSON 04

Virtual DOM이 무조건 빠르진 않다

벤치마크를 직접 짜보니, 변경이 적을 때는 압도적으로 유리하지만, 전체가 바뀌는 경우 diff 오버헤드가 추가된다. Svelte/Solid 같은 경쟁 방식이 나오는 이유를 알게 됐다.

LESSON 05

브라우저 렌더링 파이프라인 이해

Layout → Paint → Composite. 왜 Layout이 비싸고, 왜 transform은 빠른지. 프론트엔드 성능 최적화의 본질은 결국 "비싼 단계를 덜 돌리는 것"이었다.

LESSON 06

도구를 만들면 도구가 보인다

React를 쓰기만 했을 때는 "Virtual DOM이 빠르대"였다. 직접 만들어보니 "왜 빠른지, 언제 빠른지, 한계는 뭔지"가 코드 레벨에서 읽히게 됐다.

발표 Q&A — 준비했던 답변들

실제 DOM이 느린 이유가 정확히 뭔가요?
DOM은 단순한 데이터 구조가 아니라 화면과 연결된 살아있는 자료구조입니다. 텍스트 하나 바꾸는 것도 브라우저 입장에서는 "박스 크기가 달라졌나? 줄바꿈이 바뀌나? 부모 레이아웃 영향 있나?"를 전부 검토해야 합니다. 이후 Layout 재계산, Paint, Composite까지 연쇄적으로 발생할 수 있어서 비쌉니다.
Virtual DOM이 항상 빠른 건 아니지 않나요?
맞습니다. Virtual DOM도 비용이 있습니다 — VDOM 생성, diff 비교, patch 생성 등. 변경이 전체적이거나 아주 작은 앱에서는 직접 DOM 조작이 더 단순하고 빠를 수 있습니다. 그래서 Svelte나 SolidJS처럼 diff 없이 값 단위 직접 추적이나 컴파일 시 최적화하는 방식이 나온 겁니다. 벤치마크에서 직접 이 차이를 확인했습니다.
React는 CPU만 쓰고, 브라우저 렌더링은 GPU까지 쓰는 건가요?
거의 맞습니다. React의 모든 작업(컴포넌트 실행, VDOM 생성, diff)은 100% CPU(JS 엔진)에서 실행됩니다. 반면 브라우저 렌더링은 CPU(Layout, Style 계산) + GPU(Paint 일부, Composite) 둘 다 사용합니다. 특히 transform이나 opacity 같은 속성은 Layout을 건드리지 않고 GPU의 Composite 단계에서만 처리돼서 빠릅니다.
key를 안 쓰면 어떤 문제가 생기나요?
리스트에서 key가 없으면 diff 알고리즘이 "이동"을 감지하지 못합니다. 예를 들어 [A, B] → [B, A]로 순서만 바뀌어도, 인덱스 기반 비교는 "A→B로 변경, B→A로 변경"이라고 판단합니다. 결과적으로 노드를 재사용하지 못하고 전부 삭제 후 재생성하게 되어, Virtual DOM의 이점을 완전히 잃게 됩니다. key가 있으면 createChildKeyMap으로 O(1) 조회가 가능해져 정확하게 이동만 처리할 수 있습니다.

React를 사용하던 입장에서,
이제는 그 내부 구조를 설명할 수 있게 됐다.

완성도 면에서는 실제 React와 비교할 수 없지만,
이제는 React 소스코드를 보았을 때 구조가 전보다 훨씬 선명하게 읽힌다.
어떤 문제를 해결하기 위해 Virtual DOM과 Diff가 필요한지, 그리고 왜 그런 선택을 했는지도 이전보다 분명하게 이해하게 됐다.

📂 프로젝트 GitHub

github.com/choihyunjin1/week4_project

🏠 main — 메인 브랜치 🚀 DEVELOPE — 최종 완성본 ⚙️ choihyunjin — 엔진 코어 📊 choihyunjin2 — 벤치마크