DEV/React

[React 개발 기초] 9. 컴포넌트 재사용하기 (심화 Part)

develop-ch94 2026. 8. 10. 21:49

(1) 반복은 개발자의 적!

function App() {
  return (
    <div>
      <div className="user-card">
        <img src="/user1.jpg" alt="사용자1" />
        <h3>홍길동</h3>
        <p>개발자</p>
      </div>

      <div className="user-card">
        <img src="/user2.jpg" alt="사용자2" />
        <h3>김철수</h3>
        <p>디자이너</p>
      </div>

      <div className="user-card">
        <img src="/user3.jpg" alt="사용자3" />
        <h3>이영희</h3>
        <p>기획자</p>
      </div>
    </div>
  );
}

 

이 코드의 문제점

  1. 반복 지옥 : 같은 구조를 3번 복붙
  2. 수정 지옥 : 디자인 바뀌면 3곳 다 고쳐야 함
  3. 사용자 10,000명이면? 답이 없

(2) 컴포넌트로 추출하기 - Before / After

❌ Before: 반복되는 코드

<div className="user-card">
  <img src="/user1.jpg" alt="사용자1" />
  <h3>홍길동</h3>
  <p>개발자</p>
</div>

 

✅ After: 재사용 가능한 컴포넌트

// 틀 하나 만들고
function UserCard({ image, name, job }) {
  return (
    <div className="user-card">
      <img src={image} alt={name} />
      <h3>{name}</h3>
      <p>{job}</p>
    </div>
  );
}

// 마음껏 찍어내기
function App() {
  return (
    <div>
      <UserCard image="/user1.jpg" name="홍길동" job="개발자" />
      <UserCard image="/user2.jpg" name="김철수" job="디자이너" />
      <UserCard image="/user3.jpg" name="이영희" job="기획자" />
    </div>
  );
}

(3) 배열 데이터로 자동 렌더링

실무에선 데이터가 배열로 들어옴. 이럴 땐 map과 찰떡궁합임

function UserList() {
  const users = [
    { id: 1, image: '/user1.jpg', name: '홍길동', job: '개발자' },
    { id: 2, image: '/user2.jpg', name: '김철수', job: '디자이너' },
    { id: 3, image: '/user3,jpg', name: '이영희', job: '기획자' }
  ];
  
  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          image={user.image}
          name={user.name}
          job={user.job}
        />
      ))}
    </div>
  );
}

(4) 범용 Button 컴포넌트 - 실전 설계

function Button({
  children,			   // 버튼 안에 들어갈 내용
  onClick,			   // 클릭 이벤트
  variant = 'primary', // 색상 테마 (기본값)
  size = 'medium',	   // 크기 (기본값)
  disabled = false	   // 비활성화 여부
}) {
  return (
    <button
      className={`btn btn-${variant} btn-${size}`}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
    </button>
  );
}

 

다양하게 활용

<Button>기본 버튼</Button>
<Button variant="secondary">보조 버튼</Button>
<Button variant="danger" size="large">삭제</Button>
<Button disabled>비활성화</Button>
<Button onClick={handleSave}>저장</Button>

같은 Button 컴포넌트 하나로 수십 가지 스타일을 만들 수 있음

 

설계 포인트

요소 역할
children 내용은 자유롭게(이모지, 아이콘 조합 가능)
variant, size 정해진 선택지 중 고르기
기본값 (= 'primary') 안 넘겨도 동작하게
disabled 불린 props(있으면 true)

(5) 좋은 재사용 컴포넌트의 3가지 조건

1. 단일 책임 - 한 가지 역할만

// ❌ 너무 많은 일을 함
function UserCardWithSearchAndFilter() { ... }

// ✅ 하나의 역할에 집중
function UserCard() { ... }		// 카드 표시만
function SearchBar() { ... }	// 검색만
function FilterPanel() { ... }  // 필터만

 

2. 적절한 추상화 - 유연하지만 복잡하지 않게

// ❌ 너무 경직됨 - 재사용 불가
function BlueLargeBoldButton() {
  return <button style={{ color: 'blue', fontSize: 20, fontWeight: 'bold' }}>저장</button>;
}

// ❌ 너무 유연함 - Props 지옥
function Button({
  color, fontSize, fontWeight, padding, margin, border, borderRadius, shadow, hoverColor, ...
}) { ... }

// ✅ 적당히 유연함
function Button({ children, variant, size, onClick }) { ... }

 

3. Props로 커스터마이즈 - 외부에서 조정 가능

// ❌ 하드코딩
function Greeting() {
  return <h1>안녕하세요, 홍길동님!</h1>; // 이름 고정
}

// ✅ 외부에서 주입
function Greeting({ name }) {
  return <h1>안녕하세요, {name}님!</h1>;
}

(6) 자주 하는 안티패턴

❌ 안티패턴 1: 너무 이른 추출 (Premature Abstraction)

// ❌ 딱 한 곳에서만 쓰는데 굳이 컴포넌트화
function OnlyUsedOnceHeader() {
  return <h1 className="special">특별 제목</h1>;
}
문제점: 재사용 없이 그냥 파일만 늘어남. 2번 이상 반복될 때 추출하기

 

❌ 안티패턴 2: 너무 많은 Props (Props 지옥)

// ❌ 이 컴포넌트를 쓸 때마다 공포...
<Card
  title="제목"
  subtitle="부제"
  content="내용"
  footer="푸터"
  iconName="star"
  iconColor="gold"
  hasShadow={true}
  shadowLevel={3}
  borderColor="blue"
  borderWidth={2}
  padding={16}
  margin={8}
  ...
/>
해결: 관련 props는 객체로 묶거나, children을 적극 활용하기
<Card>
  <CardHeader>제목</CardHeader>
  <CardBody>내용</CardBody>
  <CardFooter>푸터</CardFooter>
</Card>​

 

❌ 안티패턴 3: 조건문 범벅 컴포넌트

// ❌ 두 완전히 다른 UI를 하나에 섞기
function UserOrAdminCard({ isAdmin, user }) {
  if (isAdmin) {
    return <div>...관리자 전용 UI 100줄...</div>;
  } else {
    return <div>...일반 유저 UI 100줄...</div>;
  }
}
문제점: 2개의 다른 컴포넌트를 억지로 합친 상태
// ✅ 두 개로 분리
function AdminCard({ user }) { ... }
function UserCard({ user }) { ... }​

 

❌ 안티패턴 4: 범용이라는 이름의 신의 컴포넌트

// ❌ "만능 컴포넌트"를 만들려는 시도
function SuperButton({ type, variant, icon, loading, tooltip, dropdown, ... }) {
  // 500줄 코드...
}
해결: 합성(Composition) 활용
<Button>
  <Icon name="save" />
  저장
</Button>​