목표: "왜 React를 쓰지?", "JSX/TSX는 뭐지?", "Vite는 뭘 해주는 거지?" 라는 질문에 스스로 답할 수 있게 된다
---
이번 강의를 마치면 여러분은 아래 질문들에 자기 언어로 설명할 수 있게 됩니다.
.js, .jsx, .ts, .tsx 파일은 도대체 뭐가 다른가?이 다섯 가지가 이 강의의 척추입니다. 하나씩 깊이 들어가봅시다.
---
여러분이 이미 알고 있을 내용을 짧게 정리합니다.
| 요소 | 역할 | 비유 |
|---|---|---|
| HTML | 페이지의 구조 (뼈대) | 신문 기사의 헤드라인, 본문, 사진 캡션 같은 구조 |
| CSS | 페이지의 외형 (스타일) | 신문 레이아웃, 폰트, 색깔, 사진 크기 |
| JavaScript | 페이지의 동작 (상호작용) | 독자가 버튼을 누르면 댓글이 펼쳐지는 그런 움직임 |
이 셋만 있으면 어떤 웹페이지도 만들 수 있습니다. 이론적으로는 그렇습니다. 그런데 왜 우리는 React, TypeScript, Vite 같은 도구를 추가로 배우는 걸까요? 그걸 이해하려면 먼저 "셋만으로 만든다는 것"의 실제 모습을 봐야 합니다.
---
"바닐라 JS(Vanilla JS)"는 별다른 라이브러리 없이 순수한 자바스크립트만 쓴다는 뜻입니다. 아이스크림에 토핑 없이 바닐라 맛만 먹는 것과 같다는 비유에서 왔습니다.
버튼을 누를 때마다 숫자가 1씩 올라가는 단순한 페이지를 만들어 봅시다.
<!-- counter.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>카운터</title>
<style>
body { font-family: sans-serif; text-align: center; margin-top: 100px; }
#count { font-size: 48px; margin: 20px; }
button { font-size: 20px; padding: 10px 20px; }
</style>
</head>
<body>
<h1>클릭 카운터</h1>
<div id="count">0</div>
<button id="increase">+1</button>
<button id="decrease">-1</button>
<button id="reset">초기화</button>
<script>
// 1. 상태(state)를 변수로 관리
let count = 0;
// 2. DOM 요소들을 미리 잡아둔다
const countEl = document.getElementById('count');
const increaseBtn = document.getElementById('increase');
const decreaseBtn = document.getElementById('decrease');
const resetBtn = document.getElementById('reset');
// 3. 화면을 갱신하는 함수
function render() {
countEl.textContent = count;
}
// 4. 이벤트 리스너를 일일이 연결
increaseBtn.addEventListener('click', () => {
count += 1;
render();
});
decreaseBtn.addEventListener('click', () => {
count -= 1;
render();
});
resetBtn.addEventListener('click', () => {
count = 0;
render();
});
</script>
</body>
</html>
이 코드는 동작합니다. 그런데 잘 보세요. 무엇을 했나요?
count라는 데이터(상태) 를 변수로 따로 만들고#count DOM 요소 를 따로 잡고render() 를 호출해서 둘을 동기화즉, "데이터" 와 "화면"이 따로 놀고, 둘을 맞추는 게 개발자 책임입니다.
지금은 버튼 3개니까 괜찮습니다. 그런데 만약 화면에 50개의 요소가 있고, 그중 7개가 count 값에 따라 바뀌어야 한다면? 또 사용자 입력이 20군데에서 들어온다면? render()를 어디서 호출해야 하는지, 어느 요소를 다시 그려야 하는지 추적하기 시작하면 코드가 폭발합니다.
이게 바로 "명령형(imperative) 프로그래밍의 한계" 입니다. 화면을 한 단계 한 단계 "이렇게 바꿔라, 저렇게 바꿔라"라고 명령해야 하니까요.
할 일을 추가하고, 삭제하고, 완료 표시하는 To-do 앱을 바닐라 JS로 짜면 어떨까요? 핵심 부분만 봅시다.
// 바닐라 JS로 짠 to-do (일부)
let todos = [];
function addTodo(text) {
todos.push({ id: Date.now(), text, done: false });
renderTodos(); // 매번 호출해야 함
}
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
renderTodos(); // 또 호출
}
function toggleTodo(id) {
const todo = todos.find(t => t.id === id);
todo.done = !todo.done;
renderTodos(); // 또또 호출
}
function renderTodos() {
const list = document.getElementById('todo-list');
list.innerHTML = ''; // 일단 다 지우고
todos.forEach(todo => {
const li = document.createElement('li'); // 다시 다 만들고
li.textContent = todo.text;
if (todo.done) li.style.textDecoration = 'line-through';
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '삭제';
deleteBtn.addEventListener('click', () => deleteTodo(todo.id));
li.appendChild(deleteBtn);
li.addEventListener('click', () => toggleTodo(todo.id));
list.appendChild(li);
});
}
문제점이 보이나요?
renderTodos() 안에서 매번 innerHTML = ''로 화면을 다 지우고 새로 그립니다 → 성능 낭비renderTodos()를 잊지 않고 호출해야 합니다 → 개발자가 일일이 챙겨야 함여기서 우리는 다음 질문에 도달합니다.
**"데이터만 바꾸면, 화면이 알아서 그에 맞게 바뀔 수는 없을까?"**
이 질문에 대한 답이 바로 React입니다.
---
| 방식 | 의미 | 예시 |
|---|---|---|
| 명령형 (Imperative) | "이렇게 해라, 저렇게 해라" 단계별 명령 | 바닐라 JS의 appendChild, innerHTML = ... |
| 선언형 (Declarative) | "결과는 이래야 한다" 최종 모습만 선언 | React의 "데이터가 X면 화면은 Y다" |
요리에 비유하면 이렇습니다.
컴포넌트는 "재사용 가능한 UI 조각" 입니다.
신문 기사 페이지를 떠올려 보세요. 거기엔 헤드라인, 기자명, 본문, 댓글, 관련기사 박스 같은 반복되거나 독립적인 단위들이 있습니다. 각각을 하나의 컴포넌트라고 생각하면 됩니다.
┌─────────────────────────────────────┐
│ <Header /> │
├─────────────────────────────────────┤
│ ┌──────────┐ ┌─────────────────┐ │
│ │<Sidebar/>│ │ <ArticleBody/> │ │
│ │ │ │ │ │
│ │ │ │ <CommentList/> │ │
│ └──────────┘ └─────────────────┘ │
├─────────────────────────────────────┤
│ <Footer /> │
└─────────────────────────────────────┘
각 컴포넌트는 자기 안의 상태(state) 와 밖에서 받는 데이터(props) 만 신경 씁니다. 마치 신문 기사 하나가 다른 기사가 어떻게 생겼는지 신경 안 써도 되는 것처럼요.
// Counter.jsx
import { useState } from 'react';
function Counter() {
// 상태 선언 - 이 한 줄이 매우 강력합니다
const [count, setCount] = useState(0);
// 화면이 어떻게 생겨야 하는지 "선언"
return (
<div style={{ textAlign: 'center', marginTop: 100 }}>
<h1>클릭 카운터</h1>
<div style={{ fontSize: 48, margin: 20 }}>{count}</div>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>초기화</button>
</div>
);
}
export default Counter;
바닐라 JS와 비교해 봅시다.
| 항목 | 바닐라 JS | React |
|---|---|---|
| 상태 관리 | let count = 0; |
const [count, setCount] = useState(0); |
| DOM 접근 | document.getElementById(...) 일일이 호출 |
필요 없음 |
| 화면 갱신 | render() 함수 수동 호출 |
setCount(...) 호출하면 React가 알아서 |
| 이벤트 연결 | addEventListener('click', ...) |
JSX 안에 onClick={...} |
핵심: setCount(count + 1) 한 줄을 호출하면, React가 "아, 상태가 바뀌었네? 그럼 이 컴포넌트의 화면을 새로 계산해서 바뀐 부분만 DOM에 반영해야겠다"라고 알아서 해줍니다. 이게 React가 해주는 일의 99%입니다.
const [count, setCount] = useState(0) 한 줄 해부이 한 줄이 React 학습의 첫 관문입니다. 다섯 조각으로 쪼개 봅시다.
(1) useState는 React의 "기억 장치"
React 컴포넌트는 사실 그냥 함수입니다. 함수는 호출이 끝나면 안의 변수가 사라지죠. 그래서 let count = 0으로 쓰면 화면이 다시 그려질 때마다 0으로 리셋됩니다. useState는 값을 React 내부에 맡겨두고, 컴포넌트가 다시 호출돼도 그 값을 꺼내 쓸 수 있게 해줍니다. 도서관 사물함에 짐을 맡기는 것과 같습니다.
(2) useState(0)의 0은 초기값
처음 한 번만 사용됩니다. 두 번째 렌더링부터는 React가 이미 저장해 둔 값을 돌려주고, 이 0은 무시됩니다. 그래서 count가 5인 상태에서 화면이 다시 그려져도 0으로 되돌아가지 않습니다.
(3) useState는 항상 "두 개짜리 배열"을 돌려준다
const result = useState(0);
// result === [0, ƒ]
// ↑ ↑
// 현재 값 그 값을 바꾸는 함수
(4) const [count, setCount] = ... 는 구조 분해 할당
JavaScript 문법입니다. 배열에서 순서대로 꺼내 이름을 붙입니다.
// 일반 예시
const fruits = ["사과", "바나나"];
const [a, b] = fruits; // a = "사과", b = "바나나"
// useState에 적용
const [count, setCount] = useState(0);
// count = 0 (첫 번째 요소)
// setCount = 갱신 함수 (두 번째 요소)
이름은 자유롭게 정할 수 있지만([숫자, 숫자변경]도 작동함), 관례적으로 [값, set값] 패턴을 씁니다. name/setName, isOpen/setIsOpen 처럼요.
(5) 왜 count = count + 1이 아니라 setCount(count + 1)인가?
두 가지 이유입니다.
count는 const로 선언돼 재할당 자체가 불가능 (일부러 막아둠)**한 문장 요약**: React에게 "0으로 초기화된 값을 기억해줘"라고 요청하고, 그 값을 읽을 변수(
count)와 바꿀 함수(setCount)를 한 줄에 받아오는 코드.
위 코드에서 함수가 HTML처럼 생긴 걸 return 하고 있죠? 이게 JSX (JavaScript XML) 입니다.
return <div>안녕하세요</div>;
이건 사실 JavaScript가 아닙니다. 브라우저는 이 문법을 모릅니다. 그래서 빌드 도구가 이 JSX를 진짜 JavaScript로 변환해줍니다:
// 위의 JSX가 빌드 후엔 이렇게 변환됨
return React.createElement('div', null, '안녕하세요');
즉, JSX는 "JavaScript 안에서 HTML처럼 보이게 써도 되는 문법적 사탕(syntactic sugar)" 입니다. 개발자가 더 직관적으로 UI를 표현할 수 있게 해주는 것뿐, 마법은 아닙니다.
**왜 JSX를 쓰는가?**
React.createElement('div', null, React.createElement('h1', null, '제목'), ...)처럼 함수 호출로 UI를 표현하면 사람이 읽기 너무 힘듭니다. HTML처럼 보이게 쓰면 한눈에 구조가 잡힙니다.
부모 컴포넌트가 자식 컴포넌트에 데이터를 넘길 때 쓰는 것이 props (properties의 줄임) 입니다.
// ArticleCard.jsx - 재사용 가능한 기사 카드 컴포넌트
function ArticleCard({ title, author, date }) {
return (
<div className="card">
<h2>{title}</h2>
<p>{author} · {date}</p>
</div>
);
}
// App.jsx - 이 컴포넌트를 여러 번 사용
function App() {
return (
<div>
<ArticleCard
title="AI가 바꾸는 저널리즘"
author="이종혁"
date="2026-05-12"
/>
<ArticleCard
title="컴퓨테이셔널 방법론의 미래"
author="홍길동"
date="2026-05-10"
/>
<ArticleCard
title="딥페이크 탐지 시스템 ER-Forensics"
author="이종혁"
date="2026-05-08"
/>
</div>
);
}
같은 ArticleCard 컴포넌트를 데이터만 바꿔서 3번 재사용했습니다. 이게 컴포넌트의 위력입니다. 신문사 CMS에서 기사 1만 건을 보여줄 때, ArticleCard 컴포넌트 하나만 잘 만들어 두면 끝입니다.
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (!input.trim()) return;
setTodos([...todos, { id: Date.now(), text: input, done: false }]);
setInput('');
};
const toggleTodo = (id) => {
setTodos(todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t
));
};
const deleteTodo = (id) => {
setTodos(todos.filter(t => t.id !== id));
};
return (
<div>
<h1>할 일 목록</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="할 일을 입력하세요"
/>
<button onClick={addTodo}>추가</button>
<ul>
{todos.map(todo => (
<li
key={todo.id}
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
>
<span onClick={() => toggleTodo(todo.id)}>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>삭제</button>
</li>
))}
</ul>
</div>
);
}
바닐라 JS의 To-do와 비교해 보세요. renderTodos() 같은 수동 렌더링 함수가 사라졌습니다. setTodos(...)만 호출하면 React가 알아서 화면을 갱신합니다. 코드가 훨씬 명료하고 짧아졌죠.
---
.js, .jsx, .ts, .tsx이제 헷갈리는 확장자들을 정리합시다. 이건 사실 매우 간단합니다.
| 확장자 | 정체 | 안에 들어가는 것 |
|---|---|---|
.js |
일반 JavaScript | 순수 JS 코드 |
.jsx |
JSX를 포함한 JavaScript | JS + JSX(HTML 같은 문법) |
.ts |
TypeScript | JS + 타입 표기 |
.tsx |
TypeScript + JSX | JS + JSX + 타입 표기 |
규칙은 단순합니다.
x가 붙는다 (jsx, tsx).t로 시작한다 (ts, tsx). 타입 없음 + JSX 없음 = .js
타입 없음 + JSX 있음 = .jsx
타입 있음(TS) + JSX 없음 = .ts
타입 있음(TS) + JSX 있음 = .tsx ← React + TS 프로젝트에서 가장 흔함
실무에서 React + TypeScript로 새 프로젝트를 시작하면, 컴포넌트 파일은 거의 다
.tsx가 됩니다. 유틸리티 함수처럼 JSX 없는 파일만.ts로 만듭니다.
---
JavaScript는 동적 타입(dynamic typing) 언어입니다. 변수에 무엇이든 넣을 수 있습니다.
let x = 5; // 숫자
x = "hello"; // 갑자기 문자열로 바뀌어도 OK
x = [1, 2, 3]; // 배열로 바뀌어도 OK
x = null; // null이어도 OK
자유롭고 편한 것 같지만, 이게 곧 버그의 온상이 됩니다.
// 사용자 정보를 받아서 화면에 표시하는 함수
function displayUserAge(user) {
return `${user.name}님은 ${user.age + 1}세가 됩니다.`;
}
// API에서 사용자 데이터를 받아왔다고 가정
const user1 = { name: "이종혁", age: 45 };
console.log(displayUserAge(user1));
// 출력: "이종혁님은 46세가 됩니다." — 정상
// 그런데 API가 age를 문자열로 줬다면?
const user2 = { name: "홍길동", age: "45" };
console.log(displayUserAge(user2));
// 출력: "홍길동님은 451세가 됩니다." — 버그!
// "45" + 1 은 JavaScript에서 "451"이 됨 (문자열 연결)
이 버그를 발견하려면 실행해보고, 화면을 직접 봐야 합니다. 운이 나쁘면 배포 후 사용자가 발견합니다. 신문사 사이트라면 "기자 평균 나이 451세" 같은 게 라이브로 떠버리는 거죠.
// user.ts
interface User {
name: string;
age: number; // 명시적으로 숫자
}
function displayUserAge(user: User): string {
return `${user.name}님은 ${user.age + 1}세가 됩니다.`;
}
const user1: User = { name: "이종혁", age: 45 }; // OK
const user2: User = { name: "홍길동", age: "45" }; // ❌ 컴파일 에러!
// ^^^^
// Type 'string' is not assignable to type 'number'.
코드를 실행하기도 전에, 에디터에서 빨간 줄과 함께 "여기 타입이 잘못됐어요"라고 알려줍니다. 배포 후 발견할 버그를 작성 단계에서 잡습니다.
interface Article {
title: string;
author: string;
publishedAt: Date;
views: number;
}
function processArticle(article: Article) {
article. // ← 여기서 점만 찍으면 에디터가
// title, author, publishedAt, views를 자동완성!
}
function calculatePolarizationScore(
before: number,
after: number,
groupSize: number
): number {
return ((after - before) / groupSize) * 100;
}
// 호출 시 인자 개수, 타입이 자동 체크됨
calculatePolarizationScore(3.2, 4.1); // ❌ 인자 부족
calculatePolarizationScore(3.2, 4.1, "30"); // ❌ 세 번째 인자가 문자열
calculatePolarizationScore(3.2, 4.1, 30); // ✅ OK
신문사 시스템이 100개 파일로 커지면, 어떤 함수의 인자를 바꾸려고 할 때 그 함수를 쓰는 모든 곳을 찾아 고쳐야 합니다. TypeScript는 그 변경이 영향을 끼치는 모든 위치를 컴파일러가 정확히 짚어줍니다.
// ArticleCard.tsx
interface ArticleCardProps {
title: string;
author: string;
date: string;
views?: number; // ?는 선택적(optional) — 없어도 됨
}
function ArticleCard({ title, author, date, views }: ArticleCardProps) {
return (
<div>
<h2>{title}</h2>
<p>{author} · {date}</p>
{views !== undefined && <span>조회수: {views}</span>}
</div>
);
}
// 사용 시
<ArticleCard title="AI 저널리즘" author="이종혁" date="2026-05-12" /> // ✅
<ArticleCard title="AI 저널리즘" author="이종혁" /> // ❌ date 필수인데 빠짐
<ArticleCard title={123} author="이종혁" date="2026-05-12" /> // ❌ title은 string인데 number
TypeScript는 JavaScript의 상위 집합(superset) 입니다. JavaScript에 타입 문법을 추가한 언어죠.
JavaScript ⊂ TypeScript
브라우저는 TypeScript를 못 읽습니다. 따라서 TypeScript 컴파일러(tsc) 가 .ts 파일을 .js 파일로 변환합니다.
// hello.ts (작성)
function greet(name: string): string {
return `안녕, ${name}!`;
}
// hello.js (컴파일 결과)
function greet(name) {
return "안녕, " + name + "!";
}
타입 정보는 컴파일 후 사라집니다. 즉, 런타임에는 그냥 JavaScript입니다. 타입 검사는 "개발 중에만" 작동하는 안전망인 것이죠.
---
지금까지 우리가 본 것들을 정리하면:
.jsx, .tsx 파일은 브라우저가 못 읽음 (JSX는 표준 JavaScript가 아님).ts, .tsx 의 타입 문법은 브라우저가 못 읽음import/export 구문도 옛날 브라우저는 못 읽음이 모든 변환 + 묶기 + 최적화 작업을 해주는 게 빌드 도구(Build Tool, Bundler) 입니다.
원본 코드 (.tsx, .ts, .css, 이미지)
↓
[빌드 도구]
↓
1. 변환 (Transpile): TS → JS, JSX → JS
2. 묶기 (Bundle): 파일 수십 개 → 하나(또는 몇 개)로
3. 최적화 (Optimize): 코드 압축, 사용 안 하는 코드 제거
4. 변경 감지 (HMR): 개발 중 코드 바꾸면 자동 새로고침
↓
브라우저가 읽을 수 있는 최종 파일 (.html, .js, .css)
| 시기 | 도구 | 특징 |
|---|---|---|
| 2010년대 초 | Grunt, Gulp | 작업 자동화에 가까움 |
| 2015~2020 | Webpack | 사실상의 표준. 강력하지만 느리고 설정 복잡 |
| 2017~ | Parcel | 설정 없이 동작. 간단함 |
| 2018~ | esbuild | Go언어로 작성, 압도적 속도 |
| 2020~ | Vite | esbuild + Rollup. 빠르고 쉬움. 현재의 사실상 표준 |
지금 새 프로젝트를 시작한다면 Vite를 쓰는 게 정답에 가깝습니다.
Vite의 핵심 아이디어는 두 가지입니다.
옛날 방식(Webpack)은 이렇습니다.
[모든 파일 분석] → [하나로 합치기] → [개발 서버 실행]
↑
이게 수십 초 걸림. 코드 한 줄 고치면 다시 반복.
Vite는 이렇게 합니다.
[개발 서버 즉시 실행] → 브라우저가 요청하는 파일만 그때그때 변환
↑
파일이 1만 개여도 처음 켜는 데 1초.
이게 가능한 이유는 현대 브라우저가 ES Modules(ESM) 을 직접 지원하기 때문입니다. import 구문을 브라우저가 알아듣기 때문에, 굳이 다 합칠 필요가 없습니다.
.tsx → .js 변환을 Vite는 esbuild라는 도구로 합니다. esbuild는 Go 언어로 작성되어 있어 JavaScript로 짠 변환기보다 10~100배 빠릅니다.
배포할 때(=운영 빌드)는 결국 모든 파일을 하나로 묶어야 합니다. 그땐 Rollup이라는 검증된 번들러를 씁니다.
즉 Vite는 "개발은 빠르게(esbuild + 네이티브 ESM), 배포는 안정적으로(Rollup)"를 조합한 것입니다.
HMR (Hot Module Replacement) 은 "코드를 저장하면 페이지 전체를 새로고침하지 않고, 바뀐 부분만 즉시 반영"하는 기능입니다.
예시: To-do 앱에서 할 일을 5개 입력해 둔 상태에서 버튼 색깔만 빨강 → 파랑으로 바꾸면
매번 새로고침하면서 클릭 5번씩 다시 하는 시간을 모으면 하루에 1시간도 절약됩니다.
명령어 4줄이면 끝납니다.
# 1. 프로젝트 생성 (대화형 메뉴가 뜸: React → TypeScript 선택)
npm create vite@latest my-app
# 2. 폴더로 이동
cd my-app
# 3. 패키지 설치
npm install
# 4. 개발 서버 실행
npm run dev
실행하면 터미널에:
VITE v5.x.x ready in 412 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
412ms. 0.4초 만에 개발 서버가 뜹니다. Webpack이었다면 보통 15~30초 걸리던 일입니다.
my-app/
├── node_modules/ ← 설치된 라이브러리들 (절대 직접 수정 안 함)
├── public/ ← 정적 파일 (이미지 등)
├── src/ ← 우리가 작성할 코드
│ ├── App.tsx ← 메인 컴포넌트
│ ├── main.tsx ← 진입점
│ └── index.css
├── index.html ← HTML 진입점
├── package.json ← 프로젝트 정보, 의존성 목록
├── tsconfig.json ← TypeScript 설정
└── vite.config.ts ← Vite 설정
npm run dev # 개발 모드: 빠르게 실행, HMR(자동 새로고침), 소스맵 포함
npm run build # 배포 모드: 압축·최적화 후 dist/ 폴더에 결과물 생성
npm run preview # build 결과(dist/)를 로컬 서버로 실행하여 배포 상태 미리 확인
npm run build 를 실행하면 dist/ 폴더에 이런 게 나옵니다:
dist/
├── index.html
├── assets/
│ ├── index-abc123.js ← 모든 컴포넌트가 하나로 묶이고 압축됨
│ ├── index-def456.css ← CSS도 합쳐지고 압축됨
│ └── logo-ghi789.png ← 이미지 등도 해시 붙어서
이 dist/ 폴더를 어떤 웹 서버에든 올리면 사이트가 동작합니다. Cloudflare Pages, Netlify, Vercel, GitHub Pages, DigitalOcean 등 어디든 가능합니다.
---
┌──────────────────────────────────────────────────────────────┐
│ 작성 시점 (개발자) │
│ │
│ Component1.tsx Component2.tsx utils.ts styles.css │
│ │ │ │ │ │
│ └────────────────┴──────────────┴───────────┘ │
│ │ │
│ [TypeScript 컴파일러] │
│ │ │
│ 타입 오류 검사 │
└────────────────────────────┬─────────────────────────────────┘
│
┌────────────────────────────▼─────────────────────────────────┐
│ 빌드 시점 (Vite) │
│ │
│ 1. TSX/TS → JS (esbuild) │
│ 2. JSX → React.createElement(...) │
│ 3. import/export 처리 │
│ 4. 파일 묶기 (Rollup) │
│ 5. 압축 및 최적화 │
└────────────────────────────┬─────────────────────────────────┘
│
┌────────────────────────────▼─────────────────────────────────┐
│ 실행 시점 (브라우저) │
│ │
│ index.html → index-abc.js + index-abc.css 로딩 │
│ → React가 컴포넌트 트리를 DOM에 그림 │
│ → 사용자 상호작용 → 상태 변경 → React가 변경분만 DOM에 반영 │
└──────────────────────────────────────────────────────────────┘
---
학생들이 자주 묻는 질문에 짧게 답합니다.
아니요. 정적 페이지나 상호작용이 거의 없는 페이지라면 HTML + CSS + 약간의 바닐라 JS로 충분합니다. 블로그 글 하나 보여주는 페이지에 React를 쓰는 건 마치 편지 한 통 부치는데 화물 트럭 부르는 것과 같습니다.
React가 빛나는 시점: 상태 변화가 많고, UI가 복잡하고, 컴포넌트를 재사용해야 할 때.
상황에 따라.
Vite는 빌드 도구입니다. Next.js, Remix는 프레임워크입니다.
신문사 사이트처럼 검색엔진 노출이 중요한 사이트는 Next.js가 강합니다. 사내 대시보드나 학습용 프로젝트는 Vite가 충분합니다.
다 비슷한 컴포넌트 기반 라이브러리/프레임워크입니다. 철학과 문법만 다릅니다.
처음 하나만 배운다면 React가 무난합니다.
---
학생들에게 다음 과제를 내주면 차이를 체감합니다.
기능
요구사항
index.html 한 파일.작성 후 다음을 비교해서 보고하세요.
displayUserAge 함수처럼 일부러 타입 불일치가 일어나는 상황을 만든 후, JavaScript 버전과 TypeScript 버전에서 각각 어떻게 동작하는지 비교 리포트를 작성하세요.
---
t-, JSX면 -x. 그래서 React+TS는 .tsx..tsx/.ts/CSS/이미지를 브라우저가 읽을 수 있는 형태로 변환·묶기·최적화.---
---