Backend
home
⚛️

React + TypeScript 연동 및 기초 문법 완전 정복

생성일
2026/04/08 12:05
태그
TypeScript
React

왜 React + TypeScript인가?

React는 현재 프론트엔드 생태계에서 가장 널리 쓰이는 UI 라이브러리다. JavaScript만으로 작성하면 props 타입 오류나 undefined 관련 버그를 런타임에야 발견하게 된다. TypeScript를 함께 쓰면 코딩 중에 오류를 미리 잡고, IDE 자동완성도 훨씬 풍부해진다.

1. 프로젝트 환경 설정 (Vite)

CRA(Create React App)는 공식 유지가 중단됐다. 현재 표준은 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. 기본 컴포넌트 작성

파일 확장자는 반드시 .tsx, 컴포넌트 이름은 대문자로 시작!
// 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
복사
상태를 직접 변경하지 말 것. count++ → 반드시 setCount(count + 1)

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 — 폼 유효성 검사