본문 바로가기

Props 다루기

2. Props 다루기

Props는 "properties"의 줄임말로, 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는 방법입니다. Props를 사용하면 컴포넌트를 재사용 가능하고 동적으로 만들 수 있습니다.

2.1 Props란 무엇인가요?

Props는 컴포넌트에 전달되는 읽기 전용 데이터입니다. 함수의 매개변수와 비슷한 역할을 합니다.

2.1.1 Props 없는 컴포넌트

function UserCard() {
  return (
    <div>
      <h3>김개발</h3>
      <p>나이: 25세</p>
      <p>직업: 프론트엔드 개발자</p>
    </div>
  );
}

2.1.2 Props를 사용한 컴포넌트

function UserCard(props) {
  return (
    <div>
      <h3>{props.name}</h3>
      <p>나이: {props.age}세</p>
      <p>직업: {props.job}</p>
    </div>
  );
}

// 사용하기
function App() {
  return (
    <div>
      <UserCard name="김개발" age={25} job="프론트엔드 개발자" />
      <UserCard name="이디자인" age={28} job="UI/UX 디자이너" />
      <UserCard name="박백엔드" age={30} job="백엔드 개발자" />
    </div>
  );
}

2.2 Props의 기본 사용법

2.2.1 문자열 Props 전달

function Greeting(props) {
  return <h1>안녕하세요, {props.name}님!</h1>;
}

function App() {
  return (
    <div>
      <Greeting name="김개발" />
      <Greeting name="이디자인" />
    </div>
  );
}

2.2.2 숫자와 불린 Props 전달

function Counter(props) {
  return (
    <div>
      <h2>카운터: {props.count}</h2>
      <p>활성화 상태: {props.isActive ? '활성' : '비활성'}</p>
    </div>
  );
}

function App() {
  return (
    <div>
      <Counter count={10} isActive={true} />
      <Counter count={0} isActive={false} />
    </div>
  );
}

2.2.3 배열과 객체 Props 전달

function SkillList(props) {
  return (
    <div>
      <h3>{props.title}</h3>
      <ul>
        {props.skills.map((skill, index) => (
          <li key={index}>{skill}</li>
        ))}
      </ul>
    </div>
  );
}

function UserProfile(props) {
  return (
    <div>
      <h2>{props.user.name}</h2>
      <p>이메일: {props.user.email}</p>
      <SkillList title="보유 기술" skills={props.user.skills} />
    </div>
  );
}

function App() {
  const userData = {
    name: '김개발',
    email: 'kim@example.com',
    skills: ['JavaScript', 'React', 'Node.js']
  };

  return (
    <div>
      <UserProfile user={userData} />
    </div>
  );
}

2.3 Props 구조 분해 할당

Props를 더 깔끔하게 사용하기 위해 구조 분해 할당을 사용할 수 있습니다.

2.3.1 기본 방법

function UserCard(props) {
  return (
    <div>
      <h3>{props.name}</h3>
      <p>나이: {props.age}세</p>
      <p>직업: {props.job}</p>
    </div>
  );
}

2.3.2 구조 분해 할당 사용

function UserCard({ name, age, job }) {
  return (
    <div>
      <h3>{name}</h3>
      <p>나이: {age}세</p>
      <p>직업: {job}</p>
    </div>
  );
}

2.3.3 기본값과 함께 구조 분해 할당

function UserCard({ name, age = 0, job = '미정', isVip = false }) {
  return (
    <div>
      <h3>{name} {isVip && '👑'}</h3>
      <p>나이: {age}세</p>
      <p>직업: {job}</p>
      {isVip && <p>VIP 회원</p>}
    </div>
  );
}

function App() {
  return (
    <div>
      <UserCard name="김개발" age={25} job="프론트엔드 개발자" isVip={true} />
      <UserCard name="이신입" /> {/* 기본값 사용 */}
      <UserCard name="박경력" age={35} job="시니어 개발자" />
    </div>
  );
}

2.4 children Props

특별한 prop인 children을 사용하면 컴포넌트 사이에 JSX를 전달할 수 있습니다.

2.4.1 기본 children 사용법

function Card({ children, title }) {
  return (
    <div>
      {title && <h3>{title}</h3>}
      <div>{children}</div>
    </div>
  );
}

function App() {
  return (
    <div>
      <Card title="공지사항">
        <p>새로운 기능이 출시되었습니다!</p>
        <button>자세히 보기</button>
      </Card>

      <Card title="사용자 정보">
        <h4>김개발</h4>
        <p>프론트엔드 개발자</p>
        <ul>
          <li>JavaScript</li>
          <li>React</li>
          <li>TypeScript</li>
        </ul>
      </Card>

      <Card>
        <p>제목 없는 카드입니다.</p>
        <img src="https://via.placeholder.com/200x100" alt="placeholder" />
      </Card>
    </div>
  );
}

2.4.2 복잡한 children 활용

function Modal({ isOpen, onClose, children }) {
  if (!isOpen) return null;

  return (
    <div>
      <div>
        <button onClick={onClose}>
          ×
        </button>
        {children}
      </div>
    </div>
  );
}

function App() {
  return (
    <div>
      <Modal isOpen={true} onClose={() => console.log('모달 닫기')}>
        <h2>알림</h2>
        <p>정말로 삭제하시겠습니까?</p>
        <div>
          <button>취소</button>
          <button>
            삭제
          </button>
        </div>
      </Modal>
    </div>
  );
}

2.5 Props 검증과 기본값

2.5.1 PropTypes를 사용한 검증 (선택사항)

// PropTypes 설치 필요: npm install prop-types
import PropTypes from 'prop-types';

function UserCard({ name, age, email, skills = [] }) {
  return (
    <div>
      <h3>{name}</h3>
      <p>나이: {age}세</p>
      <p>이메일: {email}</p>
      <div>
        <h4>보유 기술:</h4>
        <ul>
          {skills.map((skill, index) => (
            <li key={index}>{skill}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

UserCard.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  email: PropTypes.string.isRequired,
  skills: PropTypes.arrayOf(PropTypes.string)
};

2.6 실습: 제품 카드 컴포넌트

Props를 활용한 제품 카드 컴포넌트를 만들어봅시다:

function ProductCard({ 
  name, 
  price, 
  originalPrice, 
  image, 
  rating = 0, 
  reviewCount = 0, 
  isOnSale = false,
  badgeText 
}) {
  const formatPrice = (price) => {
    return price.toLocaleString() + '원';
  };

  const generateStars = (rating) => {
    return '⭐'.repeat(Math.floor(rating)) + 
           (rating % 1 !== 0 ? '✨' : '') +
           '☆'.repeat(5 - Math.ceil(rating));
  };

  const discountRate = originalPrice ? 
    Math.round(((originalPrice - price) / originalPrice) * 100) : 0;

  return (
    <div>
      {/* 배지 */}
      {badgeText && (
        <div>
          {badgeText}
        </div>
      )}

      {/* 상품 이미지 */}
      <div>
        {image}
      </div>

      {/* 상품명 */}
      <h3>
        {name}
      </h3>

      {/* 평점 */}
      {rating > 0 && (
        <div>
          <span>{generateStars(rating)}</span>
          <span>
            ({rating}) · {reviewCount}개 리뷰
          </span>
        </div>
      )}

      {/* 가격 */}
      <div>
        {isOnSale && originalPrice && (
          <div>
            {formatPrice(originalPrice)}
          </div>
        )}
        <div>
          <span>
            {formatPrice(price)}
          </span>
          {discountRate > 0 && (
            <span>
              {discountRate}% 할인
            </span>
          )}
        </div>
      </div>

      {/* 버튼 */}
      <button>
        장바구니 추가
      </button>
    </div>
  );
}

function App() {
  const products = [
    {
      name: 'MacBook Pro 16인치',
      price: 3190000,
      originalPrice: 3590000,
      image: '💻',
      rating: 4.8,
      reviewCount: 1234,
      isOnSale: true,
      badgeText: 'BEST'
    },
    {
      name: 'iPad Air',
      price: 779000,
      image: '📱',
      rating: 4.6,
      reviewCount: 856,
      isOnSale: false
    },
    {
      name: 'AirPods Pro',
      price: 329000,
      originalPrice: 359000,
      image: '🎧',
      rating: 4.5,
      reviewCount: 2103,
      isOnSale: true,
      badgeText: '신상품'
    }
  ];

  return (
    <div>
      <h1>
        온라인 스토어
      </h1>
      <div>
        {products.map((product, index) => (
          <ProductCard key={index} {...product} />
        ))}
      </div>
    </div>
  );
}

export default App;

2.7 Props 사용 시 주의사항

2.7.1 Props는 읽기 전용

// ❌ 잘못된 예 - Props 수정 시도
function UserCard({ name, age }) {
  name = name.toUpperCase(); // 이렇게 하면 안됩니다!
  return <div>{name}</div>;
}

// ✅ 올바른 예 - 새로운 변수에 저장
function UserCard({ name, age }) {
  const displayName = name.toUpperCase();
  return <div>{displayName}</div>;
}

2.7.2 적절한 Props 이름 사용

// ❌ 의미 없는 이름
<UserCard data1="김개발" data2={25} />

// ✅ 명확한 이름
<UserCard name="김개발" age={25} />

2.7.3 Props 전개 연산자 활용

const userInfo = {
  name: '김개발',
  age: 25,
  job: '프론트엔드 개발자'
};

// Props를 하나씩 전달
<UserCard name={userInfo.name} age={userInfo.age} job={userInfo.job} />

// 전개 연산자로 한 번에 전달
<UserCard {...userInfo} />

2.8 정리

Props의 핵심 내용을 학습했습니다:

  1. Props는 부모에서 자식으로 데이터를 전달하는 방법입니다
  2. 구조 분해 할당으로 Props를 깔끔하게 사용할 수 있습니다
  3. 기본값 설정으로 안정적인 컴포넌트를 만들 수 있습니다
  4. children Props로 컴포넌트 사이에 JSX를 전달할 수 있습니다
  5. Props는 읽기 전용이므로 직접 수정하면 안됩니다

다음 장에서는 Props를 활용한 더 다양한 예제들을 살펴보겠습니다.