children과 합성으로 유연하고 재사용 가능한 컴포넌트 설계하기.
컴포넌트 합성과 패턴
좋은 React 코드는 작고 명확한 컴포넌트를 조립(합성) 해 만들어집니다. 상속 대신 합성을 쓰는 React의 철학과 함께, 자주 쓰는 패턴과 폴더 구조를 정리합니다.
학습 목표
childrenprops로 내용을 끼워 넣는 합성을 이해한다.- "합성 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는 로직을 공유하는 패턴이지만, 요즘은 대부분 커스텀 훅으로 대체된다.
- 컴포넌트는 단일 책임으로 나누되, 과하게 쪼개지 않는다.
- 폴더는 "함께 바뀌는 건 함께" 두고 공용은 위로 올린다.
연습문제
title과children을 받는 재사용 가능한Modal컴포넌트를 만드세요.header,footer,children슬롯을 가진Page레이아웃을 작성하세요.- 렌더 props로 윈도우 크기를 제공하는
WindowSize컴포넌트를 만들고, 같은 로직을 커스텀 훅으로도 작성해 비교하세요. - 할 일 목록 화면을
TodoList/TodoItem/TodoForm으로 분리하고 폴더 구조를 설계하세요.
힌트 — 1번은
{children}한 줄이 핵심. 3번에서 훅 버전이 더 간결하게 쓰이는지 느껴 보세요.
💡 연습문제 풀이
불러오는 중…
댓글 0
“React.js” 강좌에 대한 댓글입니다.