구름톤 유니브☁️ 4기 FE 스터디
서론
이번 포스팅에서는 리액트에서Composition과 Inheritance를 비교하여 알아보고자 합니다.
본론
1. Composition
📖 Composition란?
여러 개의 컴포넌트를 조합해 새로운 컴포넌트를 만드는 기법으로
리액트는 컴포넌트를 가볍고 독립적인 단위로 보고, 이들을 조합해 UI를 구성하도록 설계되어 있다.
✅ Composition 방법
📌 Containment
// children prop을 사용한 FancyBorder 컴포넌트
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
// WelcomeDialog 컴포넌트
function WelcomeDialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
어서오세요
</h1>
<p className="Dialog-message">
우리 사이트에 방문하신 것을 환영합니다!
</p>
</FancyBorder>
);
}
하위 컴포넌트를 children 프로퍼티로 포함하는 가장 기본적인 합성 방법이다.
FancyBorder안의 컴포넌트들이 children이라는 props로 전달되는 것이다.
// SplitPane 컴포넌트
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
// App 컴포넌트에서 SplitPane 사용 예시
function App(props) {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
}
/>
);
}
만약 여러 개의 children 집합이 필요한 경우라면, 위 코드 처럼 left와 right라는 두 개의 props를 정의하여
각각 분리해서 렌더링하게 된다.
📌 Specialization
// Dialog 컴포넌트
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
// Dialog를 특수화한 WelcomeDialog 컴포넌트
function WelcomeDialog(props) {
return (
<Dialog
title="어서 오세요"
message="우리 사이트에 방문하신 것을 환영합니다!"
/>
);
}
범용 컴포넌트(개념)을 구별이 되도록 구체화하는 방법이다.
위 예시의 경우 WelcomeDialog(구체화된 개념)은 Dialog(범용적인 개념)의 특별한 케이스다.
기존 객체지향 개념에서는 상속을 사용하나 리액트에서는 Composition을 사용하여 Specialization을 한다.
✅ Containment와 Specialization 같이 사용하기
// 범용 Dialog 컴포넌트 (children을 사용하도록 확장)
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
// Dialog를 사용한 SignUpDialog 컴포넌트
function SignUpDialog(props) {
const [nickname, setNickname] = useState('');
const handleChange = (event) => {
setNickname(event.target.value);
};
const handleSignUp = () => {
alert(`어서 오세요, ${nickname}님!`);
};
return (
<Dialog
title="환성 탐사 프로그램"
message="닉네임을 입력해 주세요."
>
<input
value={nickname}
onChange={handleChange}
/>
<button onClick={handleSignUp}>
가입하기
</button>
</Dialog>
);
}

✅ Inheritance
📖 Inheritance(상속)이란?
상속, 다른 컴포넌트로부터 상속을 받아서 새로운 컴포넌트를 만드는 것
복잡한 컴포넌트를 쪼개서 여러개의 컴포넌트로 만들고 만든 컴포넌트를 조합해서 새로운 컴포넌트를 만들자!
라는 개념을 이해하고만 가자
📌 Composition vs Inheritance
상속은 객체지향 프로그래밍에서 클래스 기반 상속을 통해 기능을 확장하는 방식인데,
이는 리액트에서는 권장되지 않는 방식이고 리액트에서는 합성 방식을 권장한다.
왜 그럴까?
✅ 리액트에서 상속을 잘 사용하지 않는 이유
- 복잡도 증가: 컴포넌트 간 의존성이 높아져 유지보수 어려움
- 재사용성 증가: 자식 클래스에 의존적인 API 설계 필요
- 컨텍스트 공유 어려움: props·state 조합이 복잡
class BaseButton extends React.Component {
render() {
return <button {...this.props} className="base-button" />;
}
}
class IconButton extends BaseButton {
render() {
return (
<BaseButton onClick={this.props.onClick}>
<Icon name={this.props.icon} />
{this.props.children}
</BaseButton>
);
}
}
위 코드처럼 상속을 사용하면, IconButton이 BaseButton 구현에 종속적이어서 변화에 약하다는 문제점이 존재한다.
마무리
이번 글에서는 리액트에서 컴포넌트를 조합하는 기법인 Composition과 Inheritance에 대해 다뤄보았는데
전체적인 내용을 요약하자면
| Composition | Inheritance | |
| 유연성 | 매우 높음 (런타임에 조합 가능) | 낮음 (컴파일/설계 시 결정) |
| 재사용성 | 범용 컴포넌트 + 슬롯 구조 | 클래스 계층 설계 필요 |
| 유지보수성 | 낮은 결합도 → 용이 | 높은 결합도 → 어려움 |
| 결론 | ✅ 권장 | ❌ 비권장 |
오늘도 읽어주셔서 감사합니다 🫡
'TIL > React' 카테고리의 다른 글
| [React] Handling Events & Conditional Rendering & List and Keys (1) | 2025.05.06 |
|---|---|
| [React] useMemo(), useCallback(), useRef() (0) | 2025.04.14 |
| [React] JSX & Rendering Elements (0) | 2025.04.08 |
| [React] React.js란? (0) | 2025.04.08 |








