# 챕터 11: Props - 컴포넌트에 데이터 전달하기
## 서론
지금까지 우리가 만든 컴포넌트들은 항상 같은 내용만 표시했습니다. 버튼은 항상 "클릭하세요"라고 표시되고, 카드는 항상 같은 제목을 보여줬습니다. 하지만 실제 웹사이트에서는 같은 모양이지만 다른 내용을 표시해야 하는 경우가 대부분입니다. 10개의 상품 카드가 있다면, 각각 다른 상품 정보를 보여줘야 하겠죠?
이럴 때 필요한 것이 바로 Props입니다. Props는 Properties의 줄임말로, 부모 컴포넌트가 자식 컴포넌트에게 전달하는 데이터입니다. 마치 함수에 매개변수를 전달하는 것처럼, 컴포넌트에 Props를 전달하여 동적인 내용을 표시할 수 있습니다. 이번 챕터에서는 Props의 개념과 활용법을 자세히 알아보겠습니다.
## 본론
### Props란 무엇인가
Props는 컴포넌트를 더욱 유연하고 재사용 가능하게 만드는 핵심 기능입니다. HTML 태그의 속성(attribute)과 비슷한 개념으로, 컴포넌트에 데이터를 전달하는 방법입니다.
예를 들어, HTML의 img 태그를 생각해 보세요:
<img src="photo.jpg" alt="사진 설명" width="300" />
여기서 src, alt, width가 속성입니다. React 컴포넌트도 이와 비슷하게 작동합니다:
<Button text="클릭하세요" color="blue" size="large" />
### 첫 번째 Props 사용하기
기존의 Button 컴포넌트를 Props를 받도록 수정해 봅시다:
// components/Button.jsx
export default function Button(props) {
return (
<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">
{props.text}
</button>
)
}
// 사용하는 곳
import Button from '../components/Button'
export default function HomePage() {
return (
<div className="p-8">
<Button text="홈으로" />
<Button text="저장하기" />
<Button text="취소" />
</div>
)
}
이제 같은 Button 컴포넌트로 다른 텍스트를 표시할 수 있습니다!
즉, 컴포넌트 함수에선 Props로 이 함수는 이런 양식이야라고 정의를 하고, 호출할 때 미리 정한 양식대로 Props를 지정해 주면 완성입니다.
### 구조 분해 할당으로 Props 받기
Props를 더 깔끔하게 받는 방법이 있습니다. 구조 분해 할당(Destructuring)을 사용하는 것입니다:
// 방법 1: 매개변수에서 바로 분해
export default function Button({ text, color, size }) {
return (
<button className={`bg-${color}-500 text-white px-4 py-2 rounded`}>
{text}
</button>
)
}
// 방법 2: 함수 내부에서 분해
export default function Button(props) {
const { text, color, size } = props
return (
<button className={`bg-${color}-500 text-white px-4 py-2 rounded`}>
{text}
</button>
)
}
### 다양한 타입의 Props 전달하기
Props로는 문자열뿐만 아니라 숫자, 불린, 객체, 배열, 함수 등 모든 JavaScript 값을 전달할 수 있습니다:
// components/ProductCard.jsx
'use client'
export default function ProductCard({
title, // 문자열
price, // 숫자
isNew, // 불린
features, // 배열
details, // 객체
onAddToCart // 함수
}) {
return (
<div className="border rounded-lg p-4 shadow-lg">
{/* 불린 Props 활용 */}
{isNew && (
<span className="bg-green-500 text-white px-2 py-1 rounded text-sm">
NEW
</span>
)}
<h3 className="text-xl font-bold mt-2">{title}</h3>
<p className="text-2xl font-bold text-blue-600">{price}원</p>
{/* 배열 Props 활용 */}
<ul className="mt-2">
{features.map((feature, index) => (
<li key={index} className="text-gray-600">
• {feature}
</li>
))}
</ul>
{/* 객체 Props 활용 */}
<div className="text-sm text-gray-500 mt-2">
브랜드: {details.brand}<br />
카테고리: {details.category}
</div>
{/* 함수 Props 활용 */}
<button
onClick={onAddToCart}
className="mt-4 bg-blue-500 text-white px-4 py-2 rounded w-full"
>
장바구니에 추가
</button>
</div>
)
}
// 사용 예시
'use client'
import ProductCard from '../components/ProductCard'
export default function ShopPage() {
const handleAddToCart = () => {
alert('장바구니에 추가되었습니다!')
}
return (
<div className="p-8">
<ProductCard
title="무선 키보드"
price={59000}
isNew={true}
features={['블루투스 5.0', '충전식 배터리', '백라이트']}
details={{ brand: 'TechPro', category: '컴퓨터 주변기기' }}
onAddToCart={handleAddToCart}
/>
</div>
)
}
### Children Props - 특별한 Props
children은 특별한 props로, 컴포넌트 태그 사이의 내용을 받습니다:
// components/Card.jsx
export default function Card({ title, children }) {
return (
<div className="border rounded-lg shadow-lg overflow-hidden">
<div className="bg-gray-100 px-4 py-2 border-b">
<h3 className="font-bold">{title}</h3>
</div>
<div className="p-4">
{children}
</div>
</div>
)
}
// 사용 예시
import Card from '../components/Card'
export default function App() {
return (
<Card title="공지사항">
<p>이것은 카드 안의 내용입니다.</p>
<p>여러 요소를 넣을 수 있습니다.</p>
<button className="mt-2 bg-blue-500 text-white px-3 py-1 rounded">
확인
</button>
</Card>
)
}
### Props 전달 패턴들
**1. Spread 연산자로 모든 Props 전달하기:**
export default function Button(props) {
return (
<button {...props} className="bg-blue-500 text-white px-4 py-2 rounded">
{props.children}
</button>
)
}
// 사용 예시
<Button onClick={handleClick} disabled={false} type="submit">
제출
</Button>
**2. Props 객체로 전달하기:**
export default function UserList() {
const user = {
name: '홍길동',
age: 30,
email: 'hong@example.com'
}
return <UserCard {...user} />
}
### 조건부 Props
조건에 따라 다른 Props를 전달할 수 있습니다:
// components/Alert.jsx
export default function Alert({ type, message }) {
const styles = {
success: 'bg-green-100 border-green-500 text-green-700',
warning: 'bg-yellow-100 border-yellow-500 text-yellow-700',
error: 'bg-red-100 border-red-500 text-red-700',
info: 'bg-blue-100 border-blue-500 text-blue-700'
}
const icons = {
success: '✅',
warning: '⚠️',
error: '❌',
info: 'ℹ️'
}
return (
<div className={`border-l-4 p-4 ${styles[type]} mb-4`}>
<div className="flex items-center">
<span className="text-2xl mr-3">{icons[type]}</span>
<p>{message}</p>
</div>
</div>
)
}
// 사용 예시
<Alert type="success" message="저장되었습니다!" />
<Alert type="error" message="오류가 발생했습니다." />
### 실습: Props를 활용한 동적 상품 목록
// components/ProductList.jsx
export default function ProductList() {
const products = [
{
id: 1,
name: '노트북',
price: 1500000,
discount: 10,
image: '💻',
rating: 4.5
},
{
id: 2,
name: '스마트폰',
price: 1200000,
discount: 15,
image: '📱',
rating: 4.8
},
{
id: 3,
name: '태블릿',
price: 800000,
discount: 5,
image: '📱',
rating: 4.2
}
]
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 p-8">
{products.map(product => (
<ProductItem key={product.id} {...product} />
))}
</div>
)
}
// components/ProductItem.jsx
'use client'
export default function ProductItem({ name, price, discount, image, rating }) {
const discountedPrice = price * (1 - discount / 100)
return (
<div className="border rounded-lg p-4 hover:shadow-xl transition-shadow">
<div className="text-6xl text-center mb-4">{image}</div>
<h3 className="text-lg font-bold">{name}</h3>
<div className="flex items-center mt-2">
<span className="text-yellow-500">{'⭐'.repeat(Math.floor(rating))}</span>
<span className="text-sm text-gray-600 ml-2">({rating})</span>
</div>
{discount > 0 && (
<div className="mt-2">
<span className="text-gray-500 line-through">
{price.toLocaleString()}원
</span>
<span className="ml-2 text-red-500 font-bold">
{discount}% 할인
</span>
</div>
)}
<div className="text-2xl font-bold text-blue-600 mt-1">
{discountedPrice.toLocaleString()}원
</div>
<button className="w-full mt-4 bg-blue-500 hover:bg-blue-600 text-white py-2 rounded transition-colors">
구매하기
</button>
</div>
)
}
// app/page.js
import ProductList from '../components/ProductList'
export default function Home() {
return (
<div className="min-h-screen bg-gray-50">
{/* 페이지 헤더 */}
<div className="bg-white shadow-sm">
<div className="max-w-6xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-center text-gray-800">
온라인 쇼핑몰
</h1>
<p className="text-center text-gray-600 mt-2">
Props로 만든 동적 상품 목록
</p>
</div>
</div>
{/* 상품 목록 */}
<div className="max-w-6xl mx-auto">
<ProductList />
</div>
{/* 간단한 푸터 */}
<div className="text-center py-8 text-gray-500">
<p>Props를 활용한 컴포넌트 예시</p>
</div>
</div>
)
}
### Props 유효성 검사 (TypeScript 없이)
Props의 타입을 명시적으로 문서화하고 싶다면 주석을 활용할 수 있습니다:
/**
* @param {Object} props
* @param {string} props.title - 카드 제목
* @param {string} props.description - 카드 설명
* @param {string} [props.image] - 선택적 이미지 URL
* @param {Function} props.onClick - 클릭 이벤트 핸들러
* @param {boolean} [props.featured=false] - 특별 표시 여부
*/
export default function FeatureCard({
title,
description,
image,
onClick,
featured = false
}) {
return (
<div
className={`border rounded-lg p-6 cursor-pointer transition-all
${featured ? 'border-blue-500 shadow-xl' : 'border-gray-300 hover:shadow-lg'}`}
onClick={onClick}
>
{featured && (
<span className="bg-blue-500 text-white px-2 py-1 rounded text-sm">
추천
</span>
)}
{image && <img src={image} alt={title} className="w-full h-48 object-cover rounded mt-2" />}
<h3 className="text-xl font-bold mt-4">{title}</h3>
<p className="text-gray-600 mt-2">{description}</p>
</div>
)
}
## 결론
Props는 React 컴포넌트를 진정으로 재사용 가능하게 만드는 핵심 기능입니다. 이번 챕터에서 우리는 Props의 개념, 다양한 타입의 데이터 전달, 기본값 설정, children props, 그리고 여러 패턴들을 배웠습니다.
Props를 사용하면 하나의 컴포넌트로 수백, 수천 개의 다른 내용을 표시할 수 있습니다. 상품 카드 컴포넌트 하나로 전체 쇼핑몰의 상품을 표시할 수 있고, 버튼 컴포넌트 하나로 모든 버튼을 만들 수 있습니다.
Props는 단방향으로만 전달됩니다(부모→자식). 이는 데이터 흐름을 예측 가능하게 만들고 디버깅을 쉽게 합니다. 다음 챕터에서는 컴포넌트가 자체적으로 데이터를 관리하는 State에 대해 배워보겠습니다. State와 Props를 함께 사용하면 더욱 강력한 상호작용 컴포넌트를 만들 수 있습니다!
'학습자료 > Next.js 초보자 학습 과정' 카테고리의 다른 글
| # 챕터 12: State - 변하는 데이터 관리하기 (3) | 2025.08.16 |
|---|---|
| # Next.js 가이드 / 챕터 10: 첫 번째 컴포넌트 만들기 (1) | 2025.08.12 |
| # Next.js 가이드 / 챕터 9: Components - 레고 블록처럼 조립하기 (4) | 2025.08.11 |
| # Next.js 가이드 / 챕터 8: Tailwind CSS 실전 활용법 (7) | 2025.08.09 |
| # Next.js 가이드 / 챕터 7: Tailwind CSS 시작하기 (2) | 2025.08.09 |