🔍 Compound Component 패턴이란?
Compound Component(컴파운드 컴포넌트) 패턴은 부모와 자식 컴포넌트가 자연스럽게 협력하는 구조를 만들기 위한 패턴이다.
부모가 Context를 제공하고, 자식 컴포넌트들이 props 없이 그 상태를 자유롭게 사용할 수 있다.
이 패턴은 Select, Accordion, Tabs, Modal Step Form과 같은 UI를 구성할 때 자주 사용된다.
Toggle 예제를 통해 이 패턴을 이해하고, Context의 역할을 알아보자.
🔍 Context란?
Context는 여러 컴포넌트들이 데이터를 공유할 수 있도록 도와주는 기능이다.
React에서는 일반적으로 부모 컴포넌트에서 자식으로 데이터를 props를 통해 넘긴다.
하지만 컴포넌트 깊이가 깊어질수록 이 과정이 번거로워지는데, 이를 props 드릴링이라 한다.
Context를 사용하면 이 번거로움을 해결할 수 있다.
부모가 제공(provider)한 값을 자식 어디서든(useContext) 바로 꺼내 쓸 수 있기 때문이다.
const MyContext = createContext(초기값);
자식 컴포넌트에서는 다음과 같이 사용한다.
const value = useContext(MyContext);
🧩 예제 코드
- Toggle.tsx
import { createContext, useContext, useState, ReactNode } from 'react';
// context 공간을 만듦
const ToggleContext = createContext<{
on: boolean; // 불 켜짐/꺼짐 상태
toggle: () => void; // 상태를 바꾸는 함수
} | null>(null);
// 부모 컴포넌트
// 상태를 만들고 자식 컴포넌트에게 context로 전달
const Toggle = ({ children }: { children: ReactNode }) => {
const [on, setOn] = useState(false); // 상태 생성
const toggle = () => setOn(o => !o); // 상태 변경 함수
// 자식 컴포넌트들이 context 데이터에 접근 가능하도록 함
return <ToggleContext.Provider value={{ on, toggle }}>{children}</ToggleContext.Provider>;
};
// 자식 컴포넌트: On
const ToggleOn = ({ children }: { children: ReactNode }) => {
const context = useContext(ToggleContext); // context 가져옴
if (!context) throw new Error('Toggle.On must be used within <Toggle>');
return context.on ? <>{children}</> : null;
};
Toggle.On = ToggleOn;
// 자식 컴포넌트: Off
const ToggleOff = ({ children }: { children: ReactNode }) => {
const context = useContext(ToggleContext);
if (!context) throw new Error('Toggle.Off must be used within <Toggle>');
return !context.on ? <>{children}</> : null;
};
Toggle.Off = ToggleOff;
// 자식 컴포넌트: Button
const ToggleButton = () => {
const context = useContext(ToggleContext);
if (!context) throw new Error('Toggle.Button must be used within <Toggle>');
return <button onClick={context.toggle}>토글 전환</button>;
};
Toggle.Button = ToggleButton;
export default Toggle;
- CompoundExample.tsx
import Toggle from './Toggle';
// 부모 컴포넌트와 자식 컴포넌트들이 자연스럽게 함께 동작하도록 만든 패턴
// Select, Accordion, Tabs 등
const CompoundExample = () => {
return (
<div style={{ padding: 20 }}>
<h2>Compound Component 패턴 실험</h2>
<Toggle>
<Toggle.On>🔆 켜졌어요!</Toggle.On>
<Toggle.Off>🌙 꺼졌어요.</Toggle.Off>
<Toggle.Button />
</Toggle>
</div>
);
};
export default CompoundExample;
🧩 정리
- props 없이 부모 상태를 자식들이 자유롭게 사용할 수 있다.
- 복잡한 UI에서도 코드 재사용성과 유지보수성이 높아진다.
- 컴포넌트 분리: On, Off, Button이 각각 독립적으로 존재하면서 자유롭게 조합된다.
- 에러 방지: context가 없으면 명확한 에러가 발생하도록 처리하였다.
참고: context가 null인 경우 에러를 발생시키는 부분은 유지보수를 쉽게 만들어 준다.
Toggle 외부에서 잘못 사용하면 바로 알 수 있다.
✏️ (참고) 함수 이름 명시
// ❌ 익명 함수
Toggle.On = ({ ... }) => { ... };
// ✅ 이름 있는 함수
const ToggleOn = ({ ... }) => { ... };
Toggle.On = ToggleOn;
처음에는 익명 함수 형태로 작성했는데 eslint 경고가 떴다.
익명 함수(화살표 함수)는 React 개발 도구, ESLint, HMR(Hot Reload)에서 컴포넌트 이름을 제대로 인식하지 못한다고 한다.
이름 있는 함수로 선언하면 React가 컴포넌트임을 확신할 수 있다고 하여 고쳐주었다.
반응형
'Library & Runtime > React' 카테고리의 다른 글
[React] Control Props 패턴 : Controlled vs Uncontrolled (0) | 2025.05.07 |
---|---|
[React] Virtual Scroll(가상 스크롤)로 렌더링 최적화하기 (react-window) (0) | 2025.05.05 |
[React] 계산/렌더링 최적화: useMemo + React.memo 실험 (0) | 2025.05.04 |
[React] 무거운 연산 최적화 : 일반 계산 vs useMemo (0) | 2025.05.03 |