React · 2026-01-16

React: Compound Components 설계 패턴

React에서 컴포넌트 재사용성을 높이는 compound component 패턴의 개념과 구현 예제, 장단점과 접근성 및 성능 고려사항 설명

작성일 : 2026-01-16 ㆍ 작성자 : 관리자
post
목차

개요

컴포넌트 재사용은 규모가 커지는 애플리케이션에서 필수 과제다. compound component 패턴은 관련된 여러 하위 컴포넌트를 하나의 상위 컴포넌트 API로 묶어 사용성을 개선한다. 이 글에서는 개념, 구현 방법, 예제, 장단점과 접근성 고려사항을 다룬다.

왜 compound components를 사용하는가

단일 책임과 재사용성 두 가지를 동시에 만족시키기 어렵지 않게 만든다. 상위 컴포넌트가 상태와 로직을 관리하고, 하위 컴포넌트가 UI 역할만 담당하면 사용자는 선언적으로 조합해 쓸 수 있다. 또한 props drilling을 줄이고, 명확한 사용 API를 제공한다.

핵심 아이디어

핵심은 다음 세 가지이다.

  • 상태와 로직은 상위 컴포넌트에서 관리
  • 하위 컴포넌트는 상위 컴포넌트의 상태를 소비
  • 하위 컴포넌트는 상위의 컨텍스트나 클론된 엘리먼트로 연결

구현 전략

1) React Context 사용

가장 흔한 방법은 Context를 만들어 상위에서 값을 제공하고 하위에서 소비하는 방식이다. 이 방식은 명확하고 성능 최적화도 가능하다.

2) React.cloneElement 사용

상위 컴포넌트가 자식 엘리먼트를 순회하며 props를 주입하는 방법이다. 간단하지만 중첩 구조나 동적 렌더링에서는 유연성이 떨어질 수 있다.

3) Render props 대체

render props 패턴과 결합해 상태와 API를 전달할 수도 있다. 다만 API가 복잡해지면 가독성이 낮아질 수 있다.

실제 예제: Tabs 구현

아래 예제는 Context 기반의 compound components 예제다. 상위는 Tabs, 하위는 Tabs.List, Tabs.Tab, Tabs.Panel로 구성된다. JSX는 < >를 이스케이프하여 표기한다.

const TabsContext = React.createContext(null);

function Tabs({ children, defaultIndex = 0 }) {
  const [index, setIndex] = React.useState(defaultIndex);
  const value = { index, setIndex };
  return (
    <TabsContext.Provider value={value}>
      {children}
    </TabsContext.Provider>
  );
}

function TabsList({ children }) {
  return <div role="tablist">{children}</div>
}

function Tab({ children, tabIndex }) {
  const ctx = React.useContext(TabsContext);
  const selected = ctx.index === tabIndex;
  return (
    <button
      role="tab"
      aria-selected={selected}
      onClick={() => ctx.setIndex(tabIndex)}
    >
      {children}
    </button>
  );
}

function Panel({ children, panelIndex }) {
  const ctx = React.useContext(TabsContext);
  return ctx.index === panelIndex ? <div role="tabpanel">{children}</div> : null;
}

// 사용 예시
function Example() {
  return (
    <Tabs defaultIndex={0}>
      <TabsList>
        <Tab tabIndex={0}>첫 번째</Tab>
        <Tab tabIndex={1}>두 번째</Tab>
      </TabsList>

      <Panel panelIndex={0}>첫 번째 내용</Panel>
      <Panel panelIndex={1}>두 번째 내용</Panel>
    </Tabs>
  );
}

설명

위 구조는 상위가 상태를 보관하고 하위는 컨텍스트로 상태를 읽는다. 이렇게 하면 사용자가 API를 선언적으로 조합할 수 있다. 또한 하위 컴포넌트는 역할에 집중하므로 테스트와 유지보수가 쉬워진다.

장단점

  • 장점: 명확한 API, 재사용성 증대, props drilling 감소
  • 단점: Context 남발 시 리렌더링 이슈, 구조가 복잡해질 수 있음

성능 및 접근성 고려사항

Context를 쓸 때는 제공하는 값의 변경 단위를 최소화하는 것이 중요하다. 예를 들어 상태와 업데이트 함수를 별도 객체로 묶지 않거나, useMemo/useCallback으로 참조를 안정화하면 불필요한 리렌더링을 줄일 수 있다.

접근성 측면에서는 ARIA 속성과 키보드 네비게이션을 신경 써야 한다. Tabs 예제에서는 role, aria-selected, role="tabpanel" 등을 적용했고, 키보드 이벤트로 좌우 화살표 접근성을 추가하면 좋다.

베스트 프랙티스

  • API를 단순하게 유지하고 사용 예시를 문서화
  • 하위 컴포넌트는 최대한 프레젠테이션 역할만 하도록 설계
  • Context 값은 변경 빈도가 낮은 값과 업데이트 함수로 분리
  • 필요 시 cloneElement와 Context를 혼합해 유연성 확보

대체 패턴과 비교

Render props나 HOC와 비교하면 compound components는 JSX 조합이라는 자연스러운 사용성을 제공한다. 반면 작은 유틸성 컴포넌트에는 오히려 과한 설계가 될 수 있으므로 적절한 균형이 필요하다.

마무리

compound components 패턴은 리액트 컴포넌트 재사용을 위한 강력한 도구다. 적절한 Context 사용과 명확한 하위 컴포넌트 분리, 접근성 고려를 통해 유지보수성과 확장성을 확보할 수 있다. 실제 프로젝트에서는 코드 복잡도와 성능을 함께 고려하며 적용하는 것이 중요하다.

compound component 패턴 리액트 컴포넌트 재사용 compound components 예제 React 컴포넌트 컴포넌트 설계 리액트 패턴 컴포넌트 조합 리액트 컨텍스트