본문 바로가기
카테고리 없음

[톺아보기 시리즈] 컴파운드 패턴 써보셨어요?

by klm hyeon woo 2025. 1. 30.

목차

· 컴파운드 패턴이란?

· 컴파운드 패턴과 일반 방식 비교해보기

· 컴파운드 패턴을 언제 사용할 수 있을까?


컴파운드 패턴이란?

컴파운드 패턴은 React에서 관련된 여러 개의 컴포넌트를 조합하여 하나의 컴포넌트처럼 사용하는 패턴이에요. 이 패턴을 사용하면 구성 요소 간의 결합도를 낮추면서도 유연한 API를 제공할 수 있어요. 대표적인 예시로 <select> 태그 내부의 <option>을 사용하는 것처럼, 여러 컴포넌트가 자연스럽게 조합될 수 있도록 설계하는 패턴인거죠. 컴파운드 패턴의 핵심 개념은 아래와 같아요.

 

1. 컨테이너 (Component Container)

· 부모 컴포넌트가 자식 컴포넌트의 상태를 관리해요

2. 자식 컴포넌트 (Compound Components)

· 컨테이너의 상태를 활용하여 개별적으로 동작해요

3. Context API 활용 가능

· 여러 하위 컴포넌트가 상태를 공유하도록 React Context를 사용하면 상태 값을 공유할 수 있어요

4. Props Drilling 방지

· 하위 컴포넌트가 직접 필요한 상태에 접근할 수 있도록 구조화가 가능해요

컴파운드 패턴과 일반 방식 비교해보기

컴파운드 패턴과 일반적인 방식으로 사용했을 때의 차이를 한번 보고자 해요. 아래는 컴파운드 패턴을 사용하지 않고 일반적인 방법으로 만든 <Tabs> 컴포넌트예요. activeTabsetActiveTab을 계속해서 props로 전달을 해야하는 문제가 발생하기에 Props Drilling이 계속해서 발생한다는 문제점을 내포하고 있어요.

// Tabs.tsx
export function Tabs({ children, activeTab, setActiveTab }: any) {
  return (
    <div>
      {children.map((child: any) =>
        React.cloneElement(child, { activeTab, setActiveTab }),
      )}
    </div>
  );
}

// Tab.tsx
export function Tab({ children, id, activeTab, setActiveTab }: any) {
  return (
    <button
      style={{ color: activeTab === id ? "blue" : "black" }}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  );
}

// App.tsx
import { useState } from "react";
import { Tabs, Tab } from "./Tabs";

export default function App() {
  const [activeTab, setActiveTab] = useState(1);

  return (
    <Tabs activeTab={activeTab} setActiveTab={setActiveTab}>
      <Tab id={1}> Tab 1 </Tab>
      <Tab id={2}> Tab 2 </Tab>
      <Tab id={3}> Tab 3 </Tab>
    </Tabs>
  );
}

 

아시는 분들도 계시겠지만, 혹시 모르니 코드에 대한 일부 내용도 같이 첨부를 해볼게요, 이런 궁금점이 드시는 개발자분들도 계실 것 같아요.

 

Q. React.cloneElement(child, { activeTab, setActiveTab }) 을 사용했으니까 <Tab> 컴포넌트에는 명시적으로 props를 전달하지 않아도 되는건가요?

A. React.cloneElement를 사용하면 명시적으로 전달하지 않아도 Tab 컴포넌트 내부에서 사용할 수 있어요, React.cloneElement는 자식 컴포넌트에 추가적인 props를 전달하는 역할도 해요.

 

위에서 작성한 예제를 컴파운드 패턴으로 한번 변경을 해볼게요. 컴파운드 패턴으로 변경했을 때 Tabs의 상태를 Context로 관리하여 Props Drilling으로 해결을 할 수 있고, Tabs 안에서 Tab을 자연스럽게 조합하여 사용을 할 수 있어요, 더 유연하고 확장 가능한 구조를 제공할 수 있다는 장점을 가지고 있어요.

import React, { createContext, useContext, useState, ReactNode } from "react";

const TabsContext = createContext<any>(null);

function Tabs({ children }: { children: ReactNode }) {
  const [activeTab, setActiveTab] = useState(1);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div> {children} </div>
    </TabsContext.Provider>
  );
}

function Tab({ id, children }: { id: number; children: ReactNode }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);

  return (
    <button
      style={{ color: activeTab === id ? "blue" : "black" }}
      onClick={() => setActiveTab(id)}
    >
      {children}
    </button>
  );
}

export default function App() {
  return (
    <Tabs>
      <Tab id={1}> Tab 1 </Tab>
      <Tab id={2}> Tab 2 </Tab>
      <Tab id={3}> Tab 3 </Tab>
    </Tabs>
  );
}

컴파운드 패턴을 언제 사용할 수 있을까?

1. 여러 개의 관련 컴포넌트를 조합하여 하나처럼 사용을 하고 싶을 때 사용을 해요.

· Tabs, Accordion, Dropdown, Modal 등에서 자주 활용이 되어요.

2. Props Drilling을 방지하고 싶을 때 사용을 해요.

· 부모 컴포넌트에서 props를 전달하는 대신 Context를 활용하여 해결을 할 수 있어요.

3. API를 조금 더 유연하게 만들고 싶을 때 사용을 해요.

· 컴포넌트의 사용성을 높이고, 개발자가 더 쉽게 조합을 할 수 있도록 해요.

 

컴파운드 패턴을 사용하면 컴포넌트 간 결합도를 줄이면서 유연한 API를 만들 수 있어요, Context API를 활용하면 Props Drilling을 줄이고 상태를 쉽게 공유할 수 있어, 확장성 및 유지보수성이 높은 UI 컴포넌트를 만들 때 정말 유용하게 사용을 할 수 있습니다.

댓글