조건부 렌더링
function UserBadge({ isAdmin, isPremium }: { isAdmin: boolean; isPremium: boolean }) {
// 1. if/else (렌더 밖)
if (isAdmin) {
return <span className="badge-admin">관리자</span>;
}
// 2. 삼항 연산자
return (
<span className={isPremium ? "badge-premium" : "badge-free"}>
{isPremium ? "프리미엄" : "무료"}
</span>
);
}
&& 단축 평가
function Notification({ count, message }: { count: number; message?: string }) {
return (
<div>
{/* count > 0일 때만 렌더링 */}
{count > 0 && <span className="badge">{count}</span>}
{/* 주의: 0은 렌더링됨! */}
{/* ❌ */ count && <span>{count}</span>}
{/* ✅ */ count > 0 && <span>{count}</span>}
{/* 메시지가 있을 때만 */}
{message && <p>{message}</p>}
</div>
);
}
nullish 합체와 옵셔널 체이닝
function Profile({ user }: { user?: { name: string; bio?: string } }) {
return (
<div>
<h2>{user?.name ?? "익명"}</h2>
<p>{user?.bio ?? "소개글이 없습니다."}</p>
</div>
);
}
복잡한 조건 처리
type LoadState = "idle" | "loading" | "success" | "error";
function DataView({ state, data, error }: {
state: LoadState;
data: string[] | null;
error: string | null;
}) {
// switch나 객체 맵으로 처리
const content = {
idle: <p>조회 버튼을 눌러주세요.</p>,
loading: <Spinner />,
success: <DataList items={data ?? []} />,
error: <ErrorMessage message={error ?? "알 수 없는 오류"} />,
}[state];
return <div className="container">{content}</div>;
}
리스트 렌더링
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
}
function ProductList({ products }: { products: Product[] }) {
if (products.length === 0) {
return <p>상품이 없습니다.</p>;
}
return (
<ul>
{products.map(product => (
<li key={product.id}> {/* key 필수! */}
<span>{product.name}</span>
<span>{product.price.toLocaleString()}원</span>
{!product.inStock && <span>품절</span>}
</li>
))}
</ul>
);
}
key의 중요성
flowchart TB
subgraph BEFORE["렌더링 전"]
B1["key=1 철수"]
B2["key=2 영희"]
B3["key=3 민준"]
end
subgraph AFTER["영희 삭제 후"]
A1["key=1 철수"]
A3["key=3 민준"]
end
BEFORE -->|"key로 추적"| AFTER
key는 React가 리스트 항목을 추적하는 고유 식별자- 배열 인덱스를 key로 쓰면 삭제/정렬 시 버그 발생
- 데이터의 고유 ID 사용 권장
// ❌ 인덱스를 key로 사용
items.map((item, index) => <li key={index}>{item}</li>)
// ✅ 고유 ID를 key로 사용
items.map(item => <li key={item.id}>{item.name}</li>)
리스트 필터/정렬
function FilteredList({ items }: { items: Product[] }) {
const [filter, setFilter] = useState<"all" | "inStock">("all");
const [sortBy, setSortBy] = useState<"name" | "price">("name");
const displayed = items
.filter(item => filter === "all" || item.inStock)
.sort((a, b) => {
if (sortBy === "name") return a.name.localeCompare(b.name);
return a.price - b.price;
});
return (
<div>
<select onChange={e => setFilter(e.target.value as "all" | "inStock")}>
<option value="all">전체</option>
<option value="inStock">재고 있음</option>
</select>
<ul>
{displayed.map(item => (
<li key={item.id}>{item.name} — {item.price}원</li>
))}
</ul>
</div>
);
}
중첩 리스트
interface Category {
id: string;
name: string;
items: Product[];
}
function CategoryList({ categories }: { categories: Category[] }) {
return (
<div>
{categories.map(category => (
<section key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</section>
))}
</div>
);
}
정리
| 패턴 | 사용 시점 |
|---|---|
if/else | 완전히 다른 컴포넌트 반환 |
| 삼항 연산자 | 두 가지 중 하나 |
&& | 조건부 표시/숨김 |
?? | null/undefined 대체값 |
.map() | 배열 → JSX 변환 |
key | 리스트 항목 고유 식별 |
다음 편에서는 useRef와 useMemo — DOM 참조와 성능 최적화를 배웁니다.