Compound Component 패턴의 장단점과 다른 디자인 패턴과의 비교 분석 #63
justhighway
started this conversation in
TIL
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
합성 컴포넌트(Composite Component)를 구현하는 전략 중 하나인 Compound Component 패턴은 UI를 구성하는 여러 조각을 유연하고 선언적으로 만드는 방법입니다.
이 패턴의 핵심은 UI 상태와 이를 제어하는 로직을 하나의 부모 컴포넌트에서 Context로 관리하여 응집도를 높이는 것입니다.
동시에 자식 컴포넌트들은
children을 통해 전달되어 개발자가 마크업 구조와 순서를 자유롭게 결정할 수 있는 제어권을 갖게 됩니다.Dialog컴포넌트를 만들며 Compound Component 패턴을 학습한 내용을 정리하고,복잡한 UI를 구현할 때 고려할 수 있는 다른 디자인 패턴과 비교하며 각각의 장단점을 분석해 보겠습니다.
1. Compound Component 패턴의 장점
1-1. 유연한 마크업 구조와 제어권
Compound Component 패턴의 가장 큰 매력은 컴포넌트 사용자가 내부 마크업 구조를 직접 정의할 수 있다는 점 같습니다.
자식 컴포넌트의 순서를 바꾸거나 그사이에 다른 요소를 추가하는 등 UI 구조를 매우 자유롭게 구성할 수 있습니다.
모든 것을 props로 제어하는 단일 컴포넌트 방식 -
<Dialog title="..." description="..." />- 에서는 이러한 구조적 변경이 거의 불가능하다는 점을 고려하면, 유연성은 큰 장점입니다.1-2. 상태 관리 추상화
부모 컴포넌트가
isOpen과 같은 상태와close같은 로직을 모두 책임지고, 자식 컴포넌트들은 Context API를 통해 이 상태와 로직에 암묵적으로 접근합니다. 덕분에 개발자가 상태를 일일이 하위 컴포넌트로 전달하는 'Prop Drilling'의 번거로움을 피할 수 있습니다.이러한 구조를 통해, 컴포넌트를 사용하는 페이지 레벨에서 여러
useState를 선언하고 각 컴포넌트에 props로 주입해야 하는 과정을 깔끔하게 해소해 줍니다.1-3. 선언적이고 시멘틱한 API
props 기반의 API는
showCloseButton={true}나triggerElement={<Button>열기</Button>}처럼 길고 직관적이지 않은 코드를 양산하여 가독성을 해칠 수 있습니다.반면 Compound Component 패턴에서는 각 컴포넌트의 역할이 이름에 명확히 드러납니다.
<Dialog.Trigger>는 다이얼로그를 연다는 것을,<Dialog.Close>는 닫는다는 것을 이름만으로도 쉽게 유추할 수 있습니다.이러한 명명 규칙은 사실 기능이라기보다는 **관례(Convention)**에 가깝지만, 코드의 가독성을 높이고 API를 처음 접하는 개발자도 쉽게 사용법을 익히게 돕는 중요한 역할을 합니다.
1-4. 관심사 분리 (Separation of Concerns)
상태 관리와 비즈니스 로직은 부모 컴포넌트에 집중되고, UI 렌더링은 각 자식 컴포넌트(
Dialog.Trigger,Dialog.Content등)에 위임됩니다.이렇게 각 컴포넌트의 역할이 명확하게 분리되면 유지보수가 용이해집니다.
패턴 자체가 **단일 책임 원칙(SRP)**을 자연스럽게 따르는 구조이므로, 더 견고하고 확장 가능한 컴포넌트를 만들 수 있습니다.
2. Compound Component 패턴의 단점
2-1. 초기 구현의 복잡성
가장 큰 단점은 초기 구현 비용입니다.
간단한 props 기반 컴포넌트에 비해 Context 설정, 여러 개의 작은 자식 컴포넌트 정의 등 초기에 작성해야 할 코드가 많습니다.
만약 제목과 내용만 있는 간단한
Alert창처럼 기능이 매우 단순하다면 Compound Component 패턴은 오버 엔지니어링이 될 수 있습니다.2-2. 엄격한 컴포지션 구조
자식 컴포넌트들은 반드시 부모 컴포넌트 내부에서 렌더링되어야 합니다.
자식들은 부모가 제공하는 Context에 강하게 의존하기 때문에 독립적으로 사용할 수 없습니다.
이는 단점이라기보다는 패턴 고유의 제약사항에 가깝지만 설계 시 반드시 인지해야 할 부분입니다.
2-3. 과도한 Context 사용 문제 (Context Hell)
명확한 설계 없이 Context를 남용하거나, 필요 이상으로 Compound Component 패턴을 적용하면 어떤 Context가 어떤 컴포넌트에 영향을 주는지 추적하기 어려워져 디버깅이 복잡해질 수 있습니다.
물론
Dialog처럼 명확한 경계를 가진 UI에서는 문제가 될 가능성이 낮지만, 여러 컴포넌트가 중첩되고 복잡한 상호작용을 하는 구조에서는 이러한 'Context Hell'을 겪을 수 있으므로 신중한 설계가 필요합니다.3. 비교할 만한 다른 디자인 패턴
Compound Component 패턴은 "사용자에게 제어권을 얼마나 줄 것인가?" 와 "어떻게 로직을 분리하고 재사용할 것인가?" 라는 두 가지 축을 기준으로 다른 패턴들과 비교하며 이해할 수 있습니다. 가장 제어권이 적은 패턴부터 가장 많은 패턴 순으로 살펴보겠습니다.
3-1. 설정 주도 패턴 (Configuration-Driven Pattern)
가장 전통적이고 직관적인 패턴으로, 하나의 컴포넌트가 모든 것을 props를 통해 제어합니다.
필요한 모든 옵션을 props로 전달하면, 컴포넌트는 내부 로직에 따라 UI를 그립니다.
제어권/유연성: 낮음
장점
단점
대표 라이브러리: Ant Design, Material-UI (MUI)
3-2. Slot 패턴
props로 특정 이름의 "슬롯(Slot)"에 JSX 엘리먼트를 주입하는 방식입니다.
설정 주도 패턴보다는 유연하지만, 컴포넌트가 정의한 슬롯의 위치와 개수에 제약을 받습니다.
Vue.js의
<slot>에서 유래한 개념으로, React에서는children로 구현합니다.가장 일반적인 슬롯 패턴은 컴포넌트가 하나의 "내용물"을 받는 경우이며, 이때
props.children이 사용됩니다.그런데 만약
header,footer처럼 정해진 위치에 여러 개의 다른 UI 조각을 넣고 싶을 땐 어떻게 할까요?Vue.js에는
<slot name="header">와 같은 공식적인 "이름 있는 슬롯" 문법이 있지만 리액트에는 없습니다.그래서 리액트에서는 일반 props에 JSX를 전달하는 방식으로 이 패턴을 구현합니다.
이때
children은 보통 메인 컨텐츠를 위한 기본(default) 슬롯으로 사용하고, 나머지 특정 슬롯들은header,footer와 같은 명시적인 prop 이름을 사용합니다.제어권/유연성: 낮음 ~ 중간
장점
단점
header를footer뒤로 보낼 수 없음)3-3. HOC (Higher-Order Components, 고차 컴포넌트)
HOC는 컴포넌트 로직을 재사용하기 위한 클래식한 패턴입니다.
HOC는 컴포넌트를 인자로 받아, 새로운 로직과 props를 주입한 새 컴포넌트를 반환하는 '함수'입니다. UI 렌더링보다는 로직과 데이터 주입에 더 중점을 둡니다.
제어권/유연성: 중간
장점: 여러 컴포넌트에 걸쳐 동일한 로직(데이터 fetching, 상태 관리 등)을 반복 없이 재사용할 수 있습니다.
단점
3-4. Render Props 패턴
컴포넌트가 UI를 직접 렌더링하는 대신, JSX를 반환하는 함수를 prop으로 받아 실행하는 패턴입니다. 이 함수 prop에 상태와 로직을 인자로 전달함으로써, 부모의 상태를 사용해 자식 UI를 자유롭게 구성할 수 있습니다.
제어권/유연성: 높음
장점: 로직과 뷰가 명확하게 분리되며, HOC와 달리 props 충돌 문제가 없습니다. UI 구조를 매우 유연하게 구성할 수 있습니다.
단점: JSX 내부에 함수를 선언하고 그 안에 또 JSX를 작성하는 구조 때문에 가독성이 떨어지거나 'Wrapper Hell'과 유사한 중첩 구조가 생길 수 있습니다. 이 패턴 역시 Hooks의 등장으로 상당 부분 대체되었습니다.
3-5. 헤드리스 UI / Hooks 패턴
최근 주목받는 패턴으로, 로직과 상태 관리는 커스텀 훅(Hook)으로 제공하고 UI 렌더링은 100% 사용자에게 맡깁니다.
비유하자면, 자동차의 '엔진'과 '설계도'만 받고, 외관은 직접 만드는 것과 같습니다.
제어권/유연성: 매우 높음
장점
단점
헤드리스가 적용된 대표적인 라이브러리로는 TanStack-Query가 있습니다.
useQuery,useTable등도 상태 관리 로직을 훅으로 제공하고 UI 구현은 사용자에게 맡긴다는 점에서 헤드리스 철학을 공유한다고 볼 수 있습니다.4. 결론: 가이드가 있는 놀이터
Compound Component 패턴은 편의성과 유연성 사이에서 균형을 제공하는 강력한 UI 설계 전략입니다.
이 패턴의 핵심은 관련된 UI 조각들을 하나의 그룹으로 묶어, 상태 관리 로직은 부모가 책임지고(응집력), UI 구조는 사용자가 자유롭게 결정(유연성)하게 하는 것입니다. 책임이 명확하게 분리되고 선언적으로 코딩할 수 있다는 점이 매우 인상적이었습니다.
Vue.js의 Slot 패턴을 모방한 패턴도 충분히 사용해볼 수 있다고 생각하지만,
리액트는 자체적으로 합성(Composition)을 권장하기도 하고, Compound Component 패턴이라는 잘 만들어진 패턴이 있기 때문에 유연하게 UI를 구성해야 하는 경우 Slot을 굳이 사용할 필요는 없어보입니다.
언제 사용해야 할까? 🤔
이 패턴은 다음과 같은 경우에 가장 이상적이라고 생각합니다.
디자인 시스템 구축 시: Tabs, Accordion, Select, Dialog처럼 여러 개의 하위 요소로 구성되며, 일관된 동작과 함께 어느 정도의 레이아웃 자유도가 필요한 범용 컴포넌트를 만들 때
과도한 props를 피하고 싶을 때: 컴포넌트의 옵션이 prop으로 10개 이상 늘어날 것 같다면 Compound Component 패턴으로 리팩토링하는 것을 고려해볼 수 있음
결론적으로 Compound Component 패턴은 너무 엄격한 Monolithic 컴포넌트와 너무 자유로운 Headless 패턴 사이에 적절하게 위치한다고 볼 수 있을 거 같습니다. 개발자에게 적절한 가이드라인과 충분한 제어권을 동시에 제공해주기 때문에 재사용성과 확장성이 높은 컴포넌트를 만드는 효과적인 방법인 것 같습니다!
5. References
Component Patterns - patterns.dev
리액트(React) 디자인 패턴 4가지
리액트 Slot 패턴
Beta Was this translation helpful? Give feedback.
All reactions