Ch.12 React 상태 관리 — 살아 움직이는 UI
미니 프로젝트 — 투두 앱
배운 것을 모아 진짜 앱을 만든다면?
useState, 이벤트, 리스트 렌더링, 조건부 표시 — Ch11~12에서 배운 모든 것을 투두 앱 하나로 통합합니다.
여러 기능을 하나의 앱으로 조합하려면?
컴포넌트 설계 — 기능별로 컴포넌트를 나누고, state를 적절히 배치하는 것이 React의 핵심입니다.
핵심 내용
먼저 전체 구조를 설계하고 컴포넌트를 나눠봅시다
입력: 할 일 텍스트 입력 + 추가 버튼
추가: 새 할 일이 목록에 추가됨
목록: 할 일 리스트 표시 + 완료 체크
삭제/완료: 완료 토글, 삭제 기능
state는 어디에 둘까? todos 배열은 TodoApp (최상위)에 두어야 합니다. 이유: TodoForm(추가)과 TodoList(표시/삭제) 모두 todos를 사용하므로 → 공통 부모에 state를 올려놓고, props로 내려보냅니다. 이것을 '상태 끌어올리기(Lifting State Up)' 라고 합니다.
전체 앱의 구조와 state 설계를 살펴봅시다
import { useState } from "react";
function TodoApp() {
// state: 할 일 목록 배열
const [todos, setTodos] = useState([
{ id: 1, text: "React 공부하기", completed: false },
{ id: 2, text: "투두 앱 만들기", completed: false },
]);
// 다음 ID를 위한 카운터
const [nextId, setNextId] = useState(3);
// 필터 상태 (all / active / completed)
const [filter, setFilter] = useState("all");
return (
<div className="todo-app">
<h1>📝 할 일 목록</h1>
{/* 입력 폼 */}
<TodoForm onAdd={addTodo} />
{/* 필터 버튼 */}
<div className="filters">
<button onClick={() => setFilter("all")}>전체</button>
<button onClick={() => setFilter("active")}>진행 중</button>
<button onClick={() => setFilter("completed")}>완료</button>
</div>
{/* 할 일 목록 */}
<TodoList
todos={filteredTodos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
{/* 통계 */}
<p>{todos.filter(t => !t.completed).length}개 남음</p>
</div>
);
}Todo 데이터 구조 각 할 일은 객체로 관리합니다: ``` { id: 1, text: "React 공부하기", completed: false } ``` • id — 고유 식별자 (key로 사용) • text — 할 일 내용 • completed — 완료 여부 (boolean)
배열을 변경할 때는 새 배열을 만들어야 합니다 (불변성)
// ── 추가 (Create) ──
const addTodo = (text) => {
const newTodo = {
id: nextId,
text: text,
completed: false,
};
// 스프레드로 새 배열 생성 (기존 배열 수정 X)
setTodos([...todos, newTodo]);
setNextId(nextId + 1);
};
// ── 삭제 (Delete) ──
const deleteTodo = (id) => {
// filter로 해당 id를 제외한 새 배열 생성
setTodos(todos.filter(todo => todo.id !== id));
};
// ── TodoForm 컴포넌트 ──
function TodoForm({ onAdd }) {
const [text, setText] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim() === "") return; // 빈 값 방지
onAdd(text); // 부모의 addTodo 호출
setText(""); // 입력 필드 초기화
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="할 일을 입력하세요"
/>
<button type="submit">추가</button>
</form>
);
}map()으로 특정 항목만 변경한 새 배열을 만듭니다
// ── 완료 토글 (Update) ──
const toggleTodo = (id) => {
setTodos(
todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed } // 해당 항목만 변경
: todo // 나머지는 그대로
)
);
};
// ── 필터링 (파생 데이터) ──
const filteredTodos = todos.filter(todo => {
if (filter === "active") return !todo.completed;
if (filter === "completed") return todo.completed;
return true; // "all"
});
// ── TodoItem 컴포넌트 ──
function TodoItem({ todo, onToggle, onDelete }) {
return (
<li className={todo.completed ? "completed" : ""}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span style={{
textDecoration: todo.completed ? "line-through" : "none"
}}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>삭제</button>
</li>
);
}불변성 원칙 정리 React에서 state를 업데이트할 때 반드시 새 값/새 배열/새 객체를 만들어야 합니다. • 추가: `[...기존배열, 새항목]` (스프레드 연산자) • 삭제: `배열.filter(조건)` (조건에 맞는 것만 남기기) • 수정: `배열.map(항목 => 조건 ? 변경 : 그대로)` (해당 항목만 변경) 이것이 React의 불변성(Immutability) 원칙입니다!
React에서 배열 state에 새 항목을 추가할 때 올바른 방법은?
todos.push(newTodo)를 사용하면 React가 자동으로 리렌더링한다
여러 자식 컴포넌트가 공유하는 state는 ___ 에 두어야 한다
React 기초 완전 정복!
핵심 용어
TodoApp
최상위 컴포넌트 — todos state를 관리, 자식에게 props 전달
TodoForm
입력 폼 — 새 할 일 텍스트 입력과 추가 버튼
TodoList
목록 영역 — TodoItem 컴포넌트를 map()으로 렌더링
TodoItem
개별 할 일 — 완료 토글, 삭제 버튼 포함
정리 노트
미니 프로젝트 — 투두 앱 정리
CRUD 패턴
- Create
- setState([...prev, newItem])로 새 항목 추가
- Read
- state 배열을 map()으로 렌더링
- Update
- map()으로 해당 id 찾아서 속성 변경
- Delete
- filter()로 해당 id 제외한 새 배열 생성
설계 원칙
- 상태 위치
- 공통 부모(App)에 상태를 두고 자식에 props 전달
- 컴포넌트 분리
- TodoInput, TodoList, TodoItem으로 역할별 분리
- 불변 업데이트
- 배열/객체를 직접 수정하지 않고 새로 생성
CRUD는 거의 모든 앱의 기본 패턴입니다 — 투두 앱으로 완벽히 익혀두세요.
핵심 정리
- 1Ch11-1: React = 컴포넌트 기반 UI 라이브러리, SPA로 빠른 사용자 경험
- 2Ch11-2: JSX = JS 안의 HTML, Props = 부모→자식 데이터 전달
- 3Ch11-3: map()으로 리스트 렌더링, key로 요소 식별, 조건부 렌더링 3패턴
- 4Ch12-1: useState = 상태 관리 Hook, setter 호출 → 리렌더링
- 5Ch12-2: 이벤트 핸들링 + Controlled Component + 폼 처리
- 6Ch12-3: useEffect = 부수 효과 처리, 의존성 배열로 실행 조건 제어
- 7Ch12-4: 투두 앱 — CRUD, 컴포넌트 분리, 불변성 원칙, 상태 끌어올리기
- 8이제 React로 실제 웹 애플리케이션을 만들 수 있습니다!
퀴즈와 인터랙션으로 더 깊이 학습하세요
play_circle인터랙티브 레슨 시작