Ch 01. Vite로 React + TypeScript 프로젝트 생성
새 프로젝트를 시작할 때 가장 중요한 일은 발판을 제대로 만드는 것입니다. 기초가 흔들리면 나중에 모든 것을 다시 쌓아야 합니다. 이 챕터에서는 Vite로 React + TypeScript 프로젝트를 만들고, 생성된 파일들이 무슨 역할을 하는지 하나씩 이해합니다. 그리고 PART 07 전체에서 사용할 타입 정의를 미리 설계합니다.
Vite를 선택하는 이유
Create React App(CRA)은 한때 React 프로젝트의 표준이었습니다. 하지만 느린 개발 서버와 무거운 빌드 시스템 때문에 실무에서는 이미 Vite로 넘어간 지 오래입니다.
Vite는 개발 서버 실행 시 번들링을 하지 않습니다. 브라우저의 native ES 모듈을 그대로 활용하기 때문에 수백 개의 파일이 있어도 서버가 1초 안에 뜹니다. 코드를 저장하면 변경된 모듈만 교체하므로 HMR(Hot Module Replacement) 속도도 압도적으로 빠릅니다.
프로젝트 생성
터미널을 열고 다음 명령을 실행합니다.
npm create vite@latest todo-app -- --template react-tscd todo-appnpm install
--template react-ts 옵션 하나로 React와 TypeScript가 설정된 프로젝트가 만들어집니다. 생성된 폴더 구조를 살펴보겠습니다.
todo-app/
├── public/
│ └── vite.svg
├── src/
│ ├── assets/
│ │ └── react.svg
│ ├── App.css
│ ├── App.tsx
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── .gitignore
├── eslint.config.js
├── index.html
├── package.json
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
설정 파일 이해하기
tsconfig.json
TypeScript 컴파일러 설정의 진입점입니다. Vite 템플릿은 역할에 따라 설정을 두 파일로 나눕니다.
// 새 파일: tsconfig.json{ "files": [], "references": [ { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } ]}
tsconfig.app.json은 src/ 아래의 React 앱 코드에 적용되고, tsconfig.node.json은 vite.config.ts 같은 Node.js 환경 파일에 적용됩니다.
tsconfig.app.json
실제로 중요한 설정은 여기에 있습니다.
// 새 파일: tsconfig.app.json{ "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "isolatedModules": true, "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, "include": ["src"]}
중요한 옵션 몇 가지를 짚어보겠습니다.
"strict": true: strictNullChecks, noImplicitAny 등 엄격한 검사를 한 번에 켭니다. 이 옵션이 있어야 TypeScript의 진가를 발휘합니다."noEmit": true: TypeScript는 타입 검사만 담당하고, 실제 변환은 Vite(esbuild)가 합니다."jsx": "react-jsx": React 17 이후의 새로운 JSX Transform을 사용합니다.import React from 'react'를 매 파일마다 쓰지 않아도 됩니다."moduleResolution": "bundler": Vite 같은 번들러 환경에 최적화된 모듈 해석 방식입니다.
vite.config.ts
Vite 빌드 도구의 설정 파일입니다.
// 새 파일: vite.config.tsimport { defineConfig } from 'vite'import react from '@vitejs/plugin-react'export default defineConfig({ plugins: [react()],})
@vitejs/plugin-react는 Babel을 사용해 JSX를 변환하고 Fast Refresh(HMR)를 활성화합니다.
프로젝트 구조 정리
Vite가 만들어준 기본 파일을 정리하고 PART 07에서 사용할 구조를 잡겠습니다. 기존 src/ 안의 파일들을 모두 지우고 새로 만듭니다.
목표 구조는 다음과 같습니다.
src/
├── components/
│ ├── TodoForm.tsx
│ ├── TodoItem.tsx
│ └── TodoList.tsx
├── context/
│ └── TodoContext.tsx
├── hooks/
│ ├── useFetch.ts
│ ├── useLocalStorage.ts
│ └── useTodos.ts
├── types/
│ └── todo.ts
├── App.tsx
├── App.css
├── index.css
└── main.tsx
챕터가 진행될수록 이 폴더들이 하나씩 채워집니다.
타입 정의 먼저
프로젝트에서 사용할 타입을 먼저 정의합니다. 타입이 먼저 있으면 컴포넌트를 만들 때 방향이 명확해집니다.
// 새 파일: src/types/todo.tsexport type TodoStatus = 'all' | 'active' | 'completed';export interface Todo { id: number; text: string; completed: boolean; createdAt: Date;}export interface TodoState { todos: Todo[]; filter: TodoStatus;}
TodoStatus는 필터 상태를 표현하는 유니언 타입입니다. 'all' | 'active' | 'completed' 세 값만 허용하므로 오타나 잘못된 값이 들어오는 일이 없습니다.
Todo 인터페이스는 할 일 항목 하나를 표현합니다. id는 고유 식별자, text는 내용, completed는 완료 여부, createdAt은 생성 시각입니다.
TodoState는 애플리케이션의 전체 상태입니다. 이후 챕터에서 Context와 useReducer를 사용할 때 이 타입이 기준이 됩니다.
진입점 파일 설정
main.tsx와 App.tsx를 최소한의 내용으로 만들어 둡니다.
// 새 파일: src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('root 엘리먼트를 찾을 수 없습니다.');
}
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>
);
document.getElementById('root')는 null을 반환할 수 있습니다. strict 모드에서는 이 값을 그냥 createRoot에 넘기면 타입 에러가 납니다. null 체크를 하고 에러를 던지는 패턴이 실무에서 표준입니다.
// 새 파일: src/App.tsx
import './App.css';
function App() {
return (
<div className="app">
<h1>할 일 앱</h1>
<p>TypeScript + React로 만드는 Todo 앱</p>
</div>
);
}
export default App;
/* 새 파일: src/App.css */.app { max-width: 600px; margin: 0 auto; padding: 2rem; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;}h1 { font-size: 2rem; font-weight: 700; color: #1a1a2e; margin-bottom: 0.5rem;}
/* 새 파일: src/index.css */*,*::before,*::after { box-sizing: border-box; margin: 0; padding: 0;}body { background-color: #f5f5f5; color: #333; line-height: 1.6;}
개발 서버 실행
npm run dev
브라우저에서 http://localhost:5173을 열면 "할 일 앱" 제목이 보입니다. 발판이 완성되었습니다.
정리
이 챕터에서 한 일을 정리합니다.
- Vite의
react-ts템플릿으로 프로젝트를 생성했습니다. tsconfig.app.json의 핵심 옵션 —strict,noEmit,jsx— 을 이해했습니다.- PART 07 전체에서 사용할 타입(
Todo,TodoStatus,TodoState)을 먼저 정의했습니다. main.tsx에서 null 체크를 통해 타입 안전하게 DOM에 마운트했습니다.
다음 챕터에서는 이 타입들을 실제로 사용하는 컴포넌트를 만들겠습니다.