왜 React + TypeScript인가?
React는 현재 프론트엔드 생태계에서 가장 널리 쓰이는 UI 라이브러리다. JavaScript만으로 작성하면 props 타입 오류나 undefined 관련 버그를 런타임에야 발견하게 된다. TypeScript를 함께 쓰면 코딩 중에 오류를 미리 잡고, IDE 자동완성도 훨씬 풍부해진다.
1. 프로젝트 환경 설정 (Vite)
# React + TypeScript 템플릿으로 프로젝트 생성
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
Bash
복사
브라우저에서 http://localhost:5173 이 열리면 성공. src/ 폴더 안 파일을 수정하면 즉시 반영된다.
핵심 파일 구조
파일 | 역할 |
src/main.tsx | 앱 진입점 — ReactDOM.createRoot()로 렌더링 |
src/App.tsx | 루트 컴포넌트 — 개발 시작점 |
tsconfig.json | TypeScript 컴파일러 설정 |
vite.config.ts | Vite 번들러 설정 |
2. TypeScript 기초 문법
2-1. 기본 타입 선언
// 기본 타입
const name: string = "홍길동"
const age: number = 25
const isStudent: boolean = true
// 배열
const fruits: string[] = ["apple", "banana"]
const scores: number[] = [90, 85, 92]
// Union 타입 — 여러 타입 허용
const id: string | number = 123
TypeScript
복사
2-2. Interface — 객체 형태 정의
interface User {
id: number
name: string
email?: string // ? = 선택적 프로퍼티
}
const user: User = { id: 1, name: "김철수" }
TypeScript
복사
2-3. Type Alias
// 상태값처럼 정해진 문자열 목록을 타입으로 정의
type Status = "idle" | "loading" | "done"
const status: Status = "idle"
TypeScript
복사
2-4. 함수 타입
// 매개변수와 반환 타입 명시
function greet(name: string): string {
return `안녕, ${name}!`
}
// 화살표 함수
const add = (a: number, b: number): number => a + b
TypeScript
복사
2-5. 제네릭
// <T>로 타입을 매개변수처럼 사용
function identity<T>(value: T): T {
return value
}
identity<string>("hello") // 반환 타입: string
identity<number>(42) // 반환 타입: number
TypeScript
복사
3. React 함수형 컴포넌트
3-1. 기본 컴포넌트 작성
// src/components/Hello.tsx
function Hello() {
return <h1>안녕, React!</h1>
}
export default Hello
TypeScript
복사
3-2. JSX 핵심 문법
const score = 95
return (
<div className="card"> {/* class → className */}
{/* { } 안에 JS 표현식 사용 가능 */}
<p>점수: {score}</p>
<p>등급: {score >= 90 ? "A" : "B"}</p>
{/* 조건부 렌더링 */}
{score === 100 && <span>🎉 만점!</span>}
{/* 리스트 렌더링 — key 필수! */}
{["국어", "영어", "수학"].map((subject) => (
<li key={subject}>{subject}</li>
))}
</div>
)
TypeScript
복사
4. Props & Interface
Props 타입 정의하기
// Props 형태를 interface로 정의
interface UserCardProps {
name: string
role: string
avatarUrl?: string // 선택적 Props
onFollow: () => void // 함수 타입 Props
}
function UserCard({ name, role, avatarUrl, onFollow }: UserCardProps) {
return (
<div className="user-card">
{avatarUrl && <img src={avatarUrl} alt={name} />}
<h2>{name}</h2>
<p>{role}</p>
<button onClick={onFollow}>팔로우</button>
</div>
)
}
// 부모에서 사용
function App() {
return (
<UserCard
name="홍길동"
role="Frontend Developer"
onFollow={() => alert("팔로우!")}
/>
)
}
TypeScript
복사
5. useState Hook — 상태 관리
useState 타입 패턴
import { useState } from 'react'
function Counter() {
// 초기값으로 타입 자동 추론
const [count, setCount] = useState(0)
// 명시적 제네릭
const [name, setName] = useState<string>("")
// Union 타입 상태
const [status, setStatus] = useState<"idle" | "loading" | "done">("idle")
// 객체 상태
interface Form { username: string; password: string }
const [form, setForm] = useState<Form>({ username: "", password: "" })
return (
<div>
<p>카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
{/* 이전 상태 기반 업데이트는 콜백 형태 권장 */}
<button onClick={() => setCount(prev => prev - 1)}>-1</button>
<input
value={name}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setName(e.target.value)
}
/>
</div>
)
}
TypeScript
복사
6. 실전 미니 앱 — Todo List 완성 코드
지금까지 배운 interface + useState + Props를 모두 합친 Todo List 앱이다.
// src/App.tsx
import { useState } from 'react'
// ① 타입 정의
interface Todo {
id: number
text: string
done: boolean
}
// ② 자식 컴포넌트
interface TodoItemProps {
todo: Todo
onToggle: (id: number) => void
onDelete: (id: number) => void
}
function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
return (
<li style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
{todo.text}
<button onClick={() => onDelete(todo.id)}>삭제</button>
</li>
)
}
// ③ 루트 컴포넌트
function App() {
const [todos, setTodos] = useState<Todo[]>([])
const [input, setInput] = useState("")
const addTodo = () => {
if (!input.trim()) return
setTodos([...todos, { id: Date.now(), text: input, done: false }])
setInput("")
}
const toggleTodo = (id: number) =>
setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t))
const deleteTodo = (id: number) =>
setTodos(todos.filter(t => t.id !== id))
return (
<div>
<h1>📝 Todo List</h1>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTodo()}
placeholder="할 일을 입력하세요"
/>
<button onClick={addTodo}>추가</button>
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</div>
)
}
export default App
TypeScript
복사
7. 다음 단계 로드맵
•
useEffect — 사이드 이펙트(API 호출, 타이머, localStorage 연동)
•
useRef / useCallback / useMemo — 성능 최적화 훅
•
React Router — 페이지 라우팅
•
Zustand / Jotai — 전역 상태 관리
•
TanStack Query — 서버 상태 패칭 및 캐싱
•
Zod + React Hook Form — 폼 유효성 검사

