Props: 데이터 전달
Props는 부모 컴포넌트가 자식에게 전달하는 데이터입니다.
// 자식 컴포넌트
interface UserCardProps {
name: string;
email: string;
avatar?: string;
isAdmin?: boolean;
}
function UserCard({ name, email, avatar, isAdmin = false }: UserCardProps) {
return (
<div className="card">
{avatar && <img src={avatar} alt={name} />}
<h3>{name}</h3>
<p>{email}</p>
{isAdmin && <span className="badge">관리자</span>}
</div>
);
}
// 부모 컴포넌트
function App() {
return (
<div>
<UserCard name="철수" email="kim@example.com" isAdmin />
<UserCard name="영희" email="lee@example.com" />
</div>
);
}
Props는 읽기 전용
function BadComponent({ count }: { count: number }) {
// count = count + 1; // ❌ props 직접 수정 금지
return <div>{count}</div>;
}
children props
interface CardProps {
title: string;
children: React.ReactNode; // JSX를 받는 타입
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="content">{children}</div>
</div>
);
}
// 사용
<Card title="프로필">
<p>이름: 철수</p>
<p>이메일: kim@example.com</p>
</Card>
useState: 상태 관리
import { useState } from "react";
function Counter() {
// [현재값, 변경함수] = useState(초기값)
const [count, setCount] = useState(0);
return (
<div>
<p>카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(0)}>초기화</button>
</div>
);
}
상태 업데이트 원리
flowchart LR
STATE["state 변경\nsetCount(n)"]
RE["리렌더링\n컴포넌트 함수 재실행"]
DOM["DOM 업데이트\n변경된 부분만"]
STATE --> RE --> DOM
// 함수형 업데이트 (이전 값 기반)
setCount(prevCount => prevCount + 1);
// 배치 업데이트 (React 18)
// 여러 setState가 하나의 리렌더링으로 처리됨
function handleClick() {
setCount(c => c + 1); // 이 두 개가
setName("새 이름"); // 하나의 리렌더링으로 처리
}
객체 상태: 불변성
interface UserForm {
name: string;
email: string;
age: number;
}
function UserFormComponent() {
const [form, setForm] = useState<UserForm>({
name: "",
email: "",
age: 0,
});
function handleChange(field: keyof UserForm, value: string | number) {
// ❌ 직접 수정 — React가 변경을 감지하지 못함
// form.name = "새 이름";
// ✅ 스프레드로 새 객체 생성
setForm(prev => ({ ...prev, [field]: value }));
}
return (
<form>
<input
value={form.name}
onChange={e => handleChange("name", e.target.value)}
/>
</form>
);
}
배열 상태: 불변성
function TodoList() {
const [todos, setTodos] = useState<string[]>([]);
const [input, setInput] = useState("");
function addTodo() {
if (!input.trim()) return;
// ✅ 새 배열 생성
setTodos(prev => [...prev, input]);
setInput("");
}
function removeTodo(index: number) {
// ✅ filter로 새 배열
setTodos(prev => prev.filter((_, i) => i !== index));
}
return (
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={addTodo}>추가</button>
<ul>
{todos.map((todo, i) => (
<li key={i}>
{todo}
<button onClick={() => removeTodo(i)}>삭제</button>
</li>
))}
</ul>
</div>
);
}
State 끌어올리기 (Lifting State Up)
두 컴포넌트가 같은 상태를 공유해야 할 때, 공통 부모로 상태를 올립니다.
function TemperatureConverter() {
const [celsius, setCelsius] = useState(0);
const fahrenheit = (celsius * 9) / 5 + 32;
return (
<div>
<input
type="number"
value={celsius}
onChange={e => setCelsius(Number(e.target.value))}
/>
<span>°C = {fahrenheit.toFixed(1)}°F</span>
</div>
);
}
정리
| 개념 | 특징 |
|---|---|
props | 부모 → 자식, 읽기 전용 |
children | JSX를 props로 전달 |
useState | 컴포넌트 내부 상태 |
| 불변성 | 직접 수정 금지, 새 객체/배열 생성 |
| State 끌어올리기 | 공유 상태는 공통 부모로 |
다음 편에서는 이벤트 처리 — 클릭, 입력, 폼 제출 등 다양한 이벤트를 다루는 방법을 배웁니다.