dev.syw

children과 합성으로 유연하고 재사용 가능한 컴포넌트 설계하기.

컴포넌트 합성과 패턴

좋은 React 코드는 작고 명확한 컴포넌트를 조립(합성) 해 만들어집니다. 상속 대신 합성을 쓰는 React의 철학과 함께, 자주 쓰는 패턴과 폴더 구조를 정리합니다.

학습 목표

  • children props로 내용을 끼워 넣는 합성을 이해한다.
  • "합성 vs 상속"에서 왜 합성을 택하는지 안다.
  • 렌더 props 패턴으로 로직을 공유할 수 있다.
  • 컴포넌트 분리 원칙과 폴더 구조를 적용할 수 있다.

children — 내용 끼워 넣기

컴포넌트 태그 사이에 넣은 내용은 children props로 전달됩니다. "틀"은 재사용하고 "내용"만 바꿀 수 있죠.

function Card({ children }) {
  return <div className="card">{children}</div>;
}

function App() {
  return (
    <Card>
      <h2>제목</h2>
      <p>아무 내용이나 넣을 수 있어요.</p>
    </Card>
  );
}

여러 "구멍"이 필요하면 props로 JSX를 받습니다. (슬롯 패턴)

function Layout({ header, sidebar, children }) {
  return (
    <div className="layout">
      <header>{header}</header>
      <aside>{sidebar}</aside>
      <main>{children}</main>
    </div>
  );
}

<Layout header={<Logo />} sidebar={<Menu />}>
  <Article />
</Layout>

합성 vs 상속

다른 언어에서는 클래스 상속으로 기능을 확장하지만, React는 합성을 권장합니다. 특수한 컴포넌트는 일반 컴포넌트를 "안에 담아" 만듭니다.

// 일반적인 Dialog
function Dialog({ title, children }) {
  return (
    <div className="dialog">
      <h1>{title}</h1>
      {children}
    </div>
  );
}

// 특수화된 WelcomeDialog — 상속이 아니라 합성
function WelcomeDialog() {
  return (
    <Dialog title="환영합니다">
      <p>가입해 주셔서 감사합니다.</p>
    </Dialog>
  );
}

💡 TIP — "A는 B의 일종"이라고 상속을 떠올리게 되는 상황도, React에서는 거의 항상 props와 children 합성으로 더 깔끔하게 풀립니다.

렌더 props 패턴

자식에게 렌더링 방법을 함수로 넘겨, 로직은 공유하되 화면은 사용자가 결정하게 하는 패턴입니다.

import { useState, useEffect } from 'react';

function MouseTracker({ render }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  useEffect(() => {
    const onMove = (e) => setPos({ x: e.clientX, y: e.clientY });
    window.addEventListener('mousemove', onMove);
    return () => window.removeEventListener('mousemove', onMove);
  }, []);
  return render(pos); // 어떻게 그릴지는 사용자가 결정
}

// 사용
<MouseTracker render={(pos) => <p>{pos.x}, {pos.y}</p>} />

children 을 함수로 받는 변형도 흔합니다: <MouseTracker>{(pos) => ...}</MouseTracker>.

⚠️ 주의 — 요즘은 같은 로직 공유를 대부분 커스텀 훅으로 해결합니다(useMousePosition). 렌더 props는 "렌더링 시점에 값을 주입"해야 하는 특수한 경우에 여전히 유용합니다.

컴포넌트 분리 원칙

언제 컴포넌트를 쪼개야 할까요?

  • 단일 책임 — 한 컴포넌트는 한 가지 일만. 너무 많은 일을 하면 나눕니다.
  • 반복되면 추출 — 같은 JSX가 두 번 이상 보이면 컴포넌트로.
  • 이름이 붙으면 분리 — "이건 검색바, 이건 결과목록"처럼 의미 단위가 보이면 그게 컴포넌트.
  • 너무 잘게 쪼개지 말기 — 한 번만 쓰는 두 줄짜리를 굳이 빼면 오히려 읽기 어렵습니다. 균형이 중요합니다.

폴더 구조

작은 프로젝트는 단순하게, 커지면 기능(feature) 단위로 묶습니다.

src/
├─ components/      공용 UI (Button, Card, Modal)
├─ features/        기능별 묶음
│  └─ todo/
│     ├─ TodoList.jsx
│     ├─ TodoItem.jsx
│     └─ useTodos.js   기능 전용 훅
├─ hooks/           공용 커스텀 훅
├─ contexts/        Context 정의
├─ pages/           라우트 단위 화면
└─ utils/           순수 함수 유틸
원칙설명
함께 바뀌는 건 함께 둔다기능별 폴더(feature 폴더)
공용은 위로여러 곳에서 쓰면 components·hooks
파일명 = 컴포넌트명TodoItem.jsx 안에 TodoItem

💡 TIP — 정답인 구조는 없습니다. 팀이 합의한 규칙을 일관되게 지키는 게 가장 중요합니다.

요약

  • children 으로 "틀"과 "내용"을 분리해 재사용한다. 여러 구멍은 props 슬롯으로.
  • React는 상속 대신 합성으로 특수화한다.
  • 렌더 props는 로직을 공유하는 패턴이지만, 요즘은 대부분 커스텀 훅으로 대체된다.
  • 컴포넌트는 단일 책임으로 나누되, 과하게 쪼개지 않는다.
  • 폴더는 "함께 바뀌는 건 함께" 두고 공용은 위로 올린다.

연습문제

  1. titlechildren 을 받는 재사용 가능한 Modal 컴포넌트를 만드세요.
  2. header, footer, children 슬롯을 가진 Page 레이아웃을 작성하세요.
  3. 렌더 props로 윈도우 크기를 제공하는 WindowSize 컴포넌트를 만들고, 같은 로직을 커스텀 훅으로도 작성해 비교하세요.
  4. 할 일 목록 화면을 TodoList / TodoItem / TodoForm 으로 분리하고 폴더 구조를 설계하세요.

힌트 — 1번은 {children} 한 줄이 핵심. 3번에서 훅 버전이 더 간결하게 쓰이는지 느껴 보세요.

💡 연습문제 풀이

불러오는 중…

함께 보면 좋은 자료

댓글 0

React.js” 강좌에 대한 댓글입니다.

댓글을 작성하려면 로그인이 필요합니다.