개발/React

[고급반] Step 2-1. 확장성 있는 디자인 패턴: Compound Components

ophelisis 2025. 12. 23. 13:55
반응형

컴포넌트를 설계하다 보면 가끔 이런 상황에 직면합니다. 하나의 컴포넌트에 너무 많은 props를 전달하게 되어 코드가 복잡해지고, 내부 로직은 거대해지며, 조금만 요구사항이 바뀌어도 전체를 수정해야 하는 상황이죠.

이런 '거대 컴포넌트(Mega-component)'의 한계를 극복하고, 사용자에게는 자유도를, 설계자에게는 유지보수 편의성을 제공하는 Compound Components(합성 컴포넌트) 패턴을 마스터해 봅시다.


1. 🛑 "Prop Drilling"과 거대 컴포넌트의 문제점

예를 들어, 아코디언 컴포넌트를 만든다고 가정해 보겠습니다.

JavaScript
 
// ❌ 좋지 않은 예: 모든 것을 Props로 제어함
<Accordion 
  items={items} 
  allowMultiple={true} 
  headerStyle={{ color: 'blue' }}
  defaultIndex={0}
/>

이 방식은 얼핏 편해 보이지만, 특정 아이템 사이에 광고를 넣거나 헤더의 위치를 바꾸고 싶을 때 대응하기가 매우 어렵습니다. 컴포넌트 내부가 **"닫힌 구조"**이기 때문입니다.

2. ✨ Compound Components 패턴이란?

하나의 컴포넌트를 여러 개의 하위 컴포넌트로 분리하고, 이들이 서로 협력하여 하나의 기능을 수행하도록 만드는 패턴입니다. Context API를 사용하여 부모와 자식 간에 상태를 공유하는 것이 핵심입니다.

2-1. 왜 사용하는가?

  • 선언적 구조: UI의 구조를 JSX로 직접 조작할 수 있어 가독성이 뛰어납니다.
  • 유연성: 자식 컴포넌트의 순서를 바꾸거나, 그 사이에 다른 HTML 요소를 자유롭게 끼워 넣을 수 있습니다.
  • 상태 관리의 캡슐화: 외부에서 isOpen 같은 상태를 일일이 관리할 필요 없이, 컴포넌트 내부에서 알아서 처리됩니다.

3. 🛠️ 실전 구현: Toggle 컴포넌트 만들기

전형적인 Compound Components 패턴의 구현 단계는 다음과 같습니다.

1단계: Context 생성 및 부모 컴포넌트 정의

JavaScript
 
const ToggleContext = createContext();

function Toggle({ children }) {
  const [on, setOn] = useState(false);
  const toggle = () => setOn(!on);

  // 1. 상태와 로직을 Provider를 통해 공유
  return (
    <ToggleContext.Provider value={{ on, toggle }}>
      {children}
    </ToggleContext.Provider>
  );
}

2단계: 자식 컴포넌트 정의

JavaScript
 
function On({ children }) {
  const { on } = useContext(ToggleContext);
  return on ? children : null;
}

function Off({ children }) {
  const { on } = useContext(ToggleContext);
  return on ? null : children;
}

function Button() {
  const { toggle } = useContext(ToggleContext);
  return <button onClick={toggle}>스위치</button>;
}

3단계: 정적 속성으로 연결 (선택 사항이지만 추천)

JavaScript
 
Toggle.On = On;
Toggle.Off = Off;
Toggle.Button = Button;

4단계: 사용하는 쪽에서의 모습

JavaScript
 
// 사용자가 원하는 대로 레이아웃을 구성할 수 있음!
<Toggle>
  <Toggle.On>켜짐 상태입니다. 💡</Toggle.On>
  <Toggle.Off>꺼짐 상태입니다. 🌑</Toggle.Off>
  <hr />
  <Toggle.Button />
</Toggle>

4. 🎯 어떤 상황에 사용하면 좋을까요?

이 패턴은 주로 **UI 라이브러리(UI Library)**나 디자인 시스템을 구축할 때 매우 강력합니다.

  • Tabs: <Tabs><TabList><Tab /></TabList><TabPanels><Panel /></TabPanels></Tabs>
  • Select/Dropdown: <Select><Option /></Select>
  • Modal: <Modal><Header /><Content /><Footer /></Modal>

 

💡 시니어의 조언: 모든 컴포넌트를 이 패턴으로 만들 필요는 없습니다. 단순한 컴포넌트는 일반적인 Props 방식이 더 빠릅니다. 하지만 내부 요소의 순서가 자주 바뀌거나 재사용성이 극도로 높아야 하는 경우라면 반드시 고려해 보세요.

반응형