VAC + Compound Component Pattern
Overview
이 문서는 VAC(View–Asset–Container) 패턴과 Compound Component 패턴을 함께 사용하는 이유를 설명하고, 실제로 이 패턴들이 어떻게 적용되는지에 대한 예시를 제공합니다.
Why Combine VAC Pattern with the Compound Component Pattern?
VAC 패턴과 Compound Component 패턴을 함께 사용하는 이유를 이해하려면, 각 패턴이 어떤 역할을 하는지부터 알아야 합니다.
What is the Compound Component Pattern?
Compound Component Pattern은 상위 컴포넌트가 상태와 로직을 관리하고, 하위 컴포넌트들은 해당 상태를 기반으로 UI 역할을 수행하는 구조입니다. 이 방식은 UI 구성 요소를 모듈화하고 재사용성을 높이며, 컴포넌트 내부에서 Context를 통해 하위 슬롯들을 선언적으로 사용할 수 있게 해줍니다.
What is VAC Pattern?
View Asset Component (VAC) 패턴은 View 로직과 UI 표현을 명확히 분리하기 위한 설계 방식입니다. View 컴포넌트는 로직과 상태를 관리하고, VAC 컴포넌트는 오직 props 기반으로 JSX를 렌더링합니다. 핵심 규칙은 다음과 같습니다:
- 조건부 렌더링, 반복 렌더링 등 UI 렌더 제어만 담당합니다.
- 자체 상태를 가지지 않고, props만으로 동작하는 순수 컴포넌트입니다.
- 이벤트에 직접 핸들러를 바인딩하며, 부가 로직은 포함하지 않습니다.
- 상태를 가진 자식 컴포넌트를 포함할 수는 있지만, VAC 자체는 그 상태에 관여하지 않습니다.
- props는 데이터 중심보다는 UI 렌더링 중심으로 명명되어야 합니다.
- 예시:
isMax,isMin대신disabledDecrease,disabledIncreaseisLogin,isOwner를 따로 넘기는 대신,showEditButton: isLogin && isOwner
- 예시:

What are we trying to solve?
- UI 컴포넌트와 데이터 로직이 강하게 결합되어 있어 변경에 유연하지 않음
- 동일한 목적의 UI에 대해 컴포넌트 수정이나 새로운 변형(variant) 컴포넌트를 자주 생성하게 됨
- 로직 중심의 props 설계로 인해 인터페이스 변경이 빈번하게 발생함
이러한 문제를 해결하기 위해 VAC Pattern과 Compound Component Pattern을 함께 적용합니다.
So What are the Benefits?
Two Patterns Combined Visual
VAC (View–Asset–Component) 패턴과 Compound Component Pattern을 결합하면 다음과 같은 효과를 얻을 수 있습니다:
- 확장 가능(Scalable)
- 조합 가능(Composable)
- 디자인 일관성 유지(Design-consistent)
- 손쉬운 디자인 업데이트(Easy Design Update)
1. Separation of Logic and UI (via VAC)
VAC 패턴은 로직과 UI를 명확히 분리하는 구조를 지향합니다.
Benefits:
- 비즈니스 로직과 UI가 분리되어 코드 이해가 쉬워집니다.
- UI 디자인만 변경하고 싶을 때는 VAC 컴포넌트만 수정하면 됩니다.
- 데이터 중심이 아닌, 렌더링에 직관적인 props interface를 정의할 수 있습니다.
- props 기반으로만 구성되어, 전달 방식이나 내부 상태에 대해 고민할 필요가 없습니다.
2. Declarative and Flexible UI Composition (via Compound Component Pattern)
Compound Component는 Card.Header, Card.Body처럼 slot 기반 구조를 선언적으로 정의하고 재사용할 수 있습니다.
Benefits:
- 각 컴포넌트가 자체적으로 상태를 관리하고, 내부적으로 공유할 수 있어 별도의 상태 관리가 필요 없습니다.
- 상위 컴포넌트 하나만 import 하면 하위 slot 컴포넌트들도 함께 사용할 수 있어 import가 간결합니다.
- 명확한 구조로 복잡한 UI를 쉽게 작성할 수 있습니다:
import { FlyOut } from "./FlyOut";
export default function FlyoutMenu() {
return (
<FlyOut>
<FlyOut.Toggle />
<FlyOut.List>
<FlyOut.Item>Edit</FlyOut.Item>
<FlyOut.Item>Delete</FlyOut.Item>
</FlyOut.List>
</FlyOut>
);
}
- 디자인 시스템, 다중 슬롯 컴포넌트(Modal, Tabs, Cards 등)에 적합합니다.
3. UI-Centric Props Integration with Compound Components (VAC Discipline)
VAC은 UI 표현 중심의 props 인터페이스 정의를 권장하며, 이를 Compound Component에 그대로 적용할 수 있습니다.
Benefits:
- props는 데이터 구조가 아닌 UI 표현 의도에 맞춰 정의되어 직관적입니다.
- 데이터 구조가 바뀌어도 UI 디자인이 그대로라면 컴포넌트 API는 변하지 않아 인터페이스 변경이 줄어듭니다.
- 단일 props 객체로 여러 슬롯 컴포넌트들을 제어할 수 있어 복잡한 UI 구성에서도 일관성을 유지할 수 있습니다.
- 비즈니스 로직과 UI 구조가 분리되어 예측 가능하고 유지보수성이 높습니다.
VAC and Compound Component Applied Example
Goal:
다음 조건을 만족하는 VAC 스타일의 컴포넌트를 구현합니다:
- Compound Component 패턴을 사용함 (
Card.Header,Card.Body등) - Controller/View로부터 하나의 props 객체만 전달받음