React는 왜 태어났을까?
이 문서의 목표 — React가 등장한 이유를 역사 순서로 이해하고, 4대 핵심 개념(컴포넌트 · 상태 · Virtual DOM · 선언형)과 JSX가 화면에 그려지기까지의 전체 흐름을 잡습니다.
1995 — 정적인 웹 + JavaScript 탄생
초기 웹은 읽기만 가능한 문서였습니다. 넷스케이프의 브렌던 아이크가 만든 JavaScript 덕분에 버튼 클릭 같은 간단한 상호작용이 비로소 가능해졌습니다.
<button onclick="alert('Hello!')">눌러보세요</button>버튼을 누르면
onclick에 적힌 JS가 실행되어 경고창이 뜹니다. 페이지 전체를 다시 불러오지 않고 반응하는 최초의 “동적인 웹” 패턴입니다.
2000년대 중반 — AJAX와 jQuery
AJAX는 페이지 전체 새로고침 없이 필요한 부분만 갱신하게 해줬고, jQuery는 브라우저마다 다른 DOM API 차이를 숨겨 짧은 코드로 DOM을 다루게 해줬습니다.
AJAX
fetch("/user")
.then(res => res.json())
.then(data => {
document.querySelector("#name").textContent = data.name;
});서버에 비동기 요청 → 응답(JSON)을 받아 #name의 텍스트만 교체합니다.
2010년대 — 대형 웹앱과 “상태 일관성의 위기”
페이스북·넷플릭스처럼 수많은 상태가 실시간으로 변하는 앱이 등장합니다. jQuery식으로 DOM을 직접 조작하면 규모가 커질수록 변경 경로가 얽혀서 “데이터는 바뀌었는데 화면은 안 바뀌는” 상태-UI 불일치 버그가 폭증했습니다.
2013 — Facebook의 해법, React
“상태를 선언적으로 기술하면, 화면 갱신은 React가 알아서”
이를 위해 4가지 핵심 설계가 등장합니다: 컴포넌트 · 상태 · Virtual DOM · 선언형
비유 — 과거의 방식은 벽돌을 직접 쌓는 목수(명령형), React는 설계도(JSX/VDOM)만 주면 시공팀(ReactDOM)이 최적으로 시공하는 방식(선언형)입니다.
React의 4대 기둥
① 컴포넌트 (Component)
UI를 독립된 기능 단위로 나눈, 재사용 가능한 함수
function Button({ kind = "primary", children }) {
const className = kind === "primary" ? "btn-primary" : "btn-ghost";
return <button className={className}>{children}</button>;
}
function App() {
return (
<div>
<Button>좋아요</Button>
<Button kind="ghost">공유하기</Button>
</div>
);
}Button은 입력(props)에 따라 모양만 달라지는 독립 부품이고,
App은 그 부품을 조립합니다. 중복이 사라지고 유지보수가 쉬워집니다.
흔한 오해 — “컴포넌트 = 파일 1개”가 아닙니다. 핵심은 역할 단위로 나누는 것입니다.
② 상태 (State)
시간에 따라 변하는 데이터. UI가 반드시 따라야 하는 진실의 원천(source of truth)
핵심 원칙: UI는 상태의 함수입니다. 상태가 바뀌면 UI는 자동으로 일관되게 갱신됩니다.
function Counter() {
const [count, setCount] = React.useState(0);
return (
<>
<p>{count}번 클릭됨</p>
{/* ⚠️ 비권장: 같은 렌더의 count 값을 재사용 */}
<button onClick={() => setCount(count + 1)}>+1</button>
{/* ✅ 권장: 함수형 업데이트 → 직전 상태를 안전하게 참조 */}
<button onClick={() => {
setCount(c => c + 1);
setCount(c => c + 1); // 같은 이벤트 안에서도 +2 보장
}}>+2</button>
</>
);
}setCount(count + 1)을 연속 두 번 호출하면 +1만 반영될 수 있습니다.
React가 여러 업데이트를 묶어 처리(배치 업데이트)하기 때문인데,
함수형 업데이트 setCount(c => c + 1)은 항상 최신 값을 받아 안전합니다.
흔한 오해 — 상태 ≠ 전역 상태. 로컬 상태가 기본이고, 정말 필요할 때만 Context/Redux/Zustand 같은 전역 도구를 씁니다.
③ Virtual DOM
UI를 JS 객체(설계도)로 만들고, 이전 설계도와 새 설계도를 비교(diff) 해서 달라진 부분만 실제 DOM에 반영(패치)하는 전략
(이전 VDOM) vs (새 VDOM)
│ diff
▼
최소 변경 목록(패치)
│
▼
실제 DOM에 적용바닐라 DOM 직접 조작
const title = document.createElement("h1");
title.textContent = "Hello!";
document.body.appendChild(title);
title.textContent = "Hi!"; // 시점·순서를 전부 개발자가 직접 관리흔한 오해 — VDOM이 DOM을 대체하는 게 아닙니다. 최종 결과물은 언제나 실제 DOM이고, VDOM은 “무엇이 바뀌었나”를 계산하는 용도입니다.
④ 선언형 UI
무엇을 보여줄지(상태 → UI 규칙)만 선언하면, 어떻게 DOM을 조작할지는 React가 알아서 처리
명령형 (Imperative)
// 절차를 하나하나 직접 지시
const btn = document.querySelector('#btn');
const panel = document.querySelector('#panel');
btn.addEventListener('click', () => {
panel.style.display =
panel.style.display === 'none' ? 'block' : 'none';
});DOM의 현재 상태를 개발자가 계속 추적해야 합니다.
흔한 오해 — “선언형은 느리다”? 추상화 비용은 있지만, 규모가 커질수록 유지보수 효율과 버그 감소 이점이 훨씬 큽니다.
렌더 파이프라인: JSX가 화면이 되기까지
JSX (사람 친화적 표기)
└─ Babel (변환)
└─ React.createElement(...) → Virtual DOM (설계도)
└─ ReactDOM.createRoot / render → 실제 DOM 최소 패치JSX 작성
JS 안에서 HTML처럼 UI를 쓰는 문법 설탕. {}로 JS 표현식을 넣을 수 있습니다.
const name = "홍길동";
const el = <h1>Hello, {name.toUpperCase()}!</h1>;JSX는 문자열도 아니고, 브라우저가 직접 이해하지 못합니다. 반드시 변환이 필요합니다.
Babel이 변환
JSX와 최신 JS 문법을 브라우저가 아는 코드로 바꿔주는 통역사입니다.
// 입력
const el = <h1>Hello</h1>;
// Babel 출력 (개념)
const el = React.createElement("h1", null, "Hello");createElement가 VDOM 객체 생성
실제 DOM이 아닌 평범한 JS 객체를 만듭니다. 이 객체 트리가 곧 설계도입니다.
{
type: "h1",
key: null,
ref: null,
props: { children: "Hello" }
}ReactDOM이 실제 DOM에 반영
createRoot로 React가 관리할 루트를 만들고(React 18+),
render가 VDOM을 비교해 필요한 부분만 실제 DOM에 그립니다.
직접 돌려보기 — CDN + Babel 최소 실행
빌드 도구 없이 HTML 한 장으로 원리를 체험할 수 있습니다.
- server.js
- index.html
public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hello React with Babel</title>
<!-- React & ReactDOM: VDOM 생성 + DOM 패치 엔진 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- Babel: JSX → 브라우저 호환 코드 -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<!-- type="text/babel"이 없으면 브라우저가 JSX를 해석하지 못해 실패! -->
<script type="text/babel">
function Counter() {
const [count, setCount] = React.useState(0);
return (
<section>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</section>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Counter />);
</script>
</body>
</html>학습 경로 — ① CDN + Babel Standalone으로 원리 체득 → ② Vite/CRA로 핫리로드·빌드·TS까지 갖춘 실무 환경으로 이동. Babel Standalone은 런타임에 매번 변환하므로 개발 학습용이며, 실무 번들링과는 다릅니다.
언제 React가 좋은 선택일까?
| 상황 | 판단 |
|---|---|
| 상태 변화가 잦은 대규모 앱 (대시보드, SNS, 커머스) | ✅ React가 거의 필수 |
| 실시간 데이터 갱신, 복잡한 사용자 인터랙션 | ✅ 적합 |
| 정적이고 작은 페이지, 수명이 짧은 프로젝트 | ⚠️ 과할 수 있음 |
용어 정리
핵심 용어 글로서리 펼치기
| 용어 | 한 줄 정의 |
|---|---|
| JSX | JS 안에 HTML처럼 UI를 쓰는 확장 문법. {}로 표현식 삽입 |
| Babel | JSX/최신 문법을 브라우저 호환 코드로 바꾸는 트랜스파일러 |
| Virtual DOM | 화면 구조의 JS 객체 설계도. diff 계산 후 최소 패치용 |
| Diffing | 이전/다음 VDOM 비교. 같은 타입은 재사용, 다르면 교체 |
| Batching | 여러 상태 변경을 묶어 렌더 횟수를 줄이는 최적화 |
| Component | 재사용 가능한 UI 단위 (함수 컴포넌트 중심) |
| Props | 부모 → 자식으로 전달하는 읽기 전용 입력값 |
| State | 컴포넌트 내부의 변할 수 있는 데이터 |
| createRoot / render | React 루트 생성(18+) 후 VDOM을 실제 DOM에 반영 |
| Bundler | 모듈 묶기 + HMR/빌드/최적화 (Webpack, Vite) |
| CDN | 라이브러리를 네트워크로 제공하는 분산 전송망 |
| Express | Node 기반 경량 서버 (정적 파일·간단 API) |
자가 점검 체크리스트
- 역사 → 문제 → 해결(React) 흐름을 1분 안에 설명할 수 있다
-
JSX → Babel → createElement → VDOM → render흐름을 그림으로 그릴 수 있다 - 명령형 vs 선언형 차이를 코드로 시연할 수 있다
- CDN+Babel 최소 실행 → Vite/CRA 실무 환경 경로를 설명할 수 있다
- “언제 React가 과한가”를 기준을 들어 설명할 수 있다