하나의 복합적인 UI를 여러 개의 관련된 컴포넌트로 분리하고,
이들을 하나의 API로 묶어 사용하는 쪽에서 원하는 구조로 조립할 수 있는 컴포넌트 설계 패턴즉, 관련된 컴포넌트들을 하나의 기능을 구성하는 부품으로 묶었기 때문에 서로 상태를 공유할 수 있다.
일반적으로 컴포넌트 제작 시, 가장 보편적으로 사용하는 패턴은 Control Props 일 것이다.
<input
value={value}
onChange={setValue}
/>
이런 식으로 정의된 패턴을 주로 사용했을 것이다.
그러나 이러한 패턴은 로직이 추가될수록,
- 기존 컴포넌트가 감당해야할 내용이 많아짐
- 기존 컴포넌트 자체에 수정이 필요함
의 문제를 가지고 있다.
따라서 이 부분을 완화할 수 있는 것이 바로 Compound Component 패턴이다.
처음 이 개념을 접했을 때, 막연히 "부모 컴포넌트에 속한 하위(자식) 컴포넌트" 정도로 다가왔다.
children과의 차이점을 집어보면서 그 개념을 정립할 수 있었다.
부모 컴포넌트 태그 사이에 들어올 내용을 전달받는 props
즉, 부모 컴포넌트라는 껍데기 안에 단순히 원하는 내용을 넣을 수 있다는 것이다.
다르게 말하자면, 부모 컴포넌트와는 독립적으로 존재하는 컴포넌트라고 볼 수 있다.
앞서 언급했듯이,
관련된 컴포넌트들을 하나의 시스템으로 묶는 설계 패턴 이다.
따라서, 하위에 들어갈 자식 컴포넌트 요소들이 독립적인 존재라기 보단,
라는 부수적인 효과가 붙는 것이 근본적인 차이이다.
사실 특별한 문법이 있는 것은 아니다.
서로 연관이 있는 일반 컴포넌트를 여러 개 만든 다음,
부모 컴포넌트와 관련된 하위 컴포넌트라는 관계를 API로 표현하면 된다.
먼저 각각의 컴포넌트를 만든다.
import type { ReactNode } from 'react';
type Props = {
children: ReactNode;
};
function CardRoot({ children }: Props) {
return (
<div className="card">
{children}
</div>
);
}
function CardTitle({ children }: Props) {
return (
<h2 className="card-title">
{children}
</h2>
);
}
function CardDescription({ children }: Props) {
return (
<p className="card-description">
{children}
</p>
);
}
가장 단순한 방법은 자식 컴포넌트를 부모 객체의 프로퍼티처럼 묶는 것이다.
const Card = {
Root: CardRoot,
Title: CardTitle,
Description: CardDescription,
};
그러면 다음과 같은 구조가 된다.
Card
├── Root → CardRoot
├── Title → CardTitle
└── Description → CardDescription
예를 들면,
<Card.Title />
은 Card.Title이 가리키는 CardTitle 컴포넌트를 렌더링한다.
즉, 개념적으로
<Card.Title />은
<CardTitle />과 같은 컴포넌트를 렌더링한다.
문제점
이때 Card 자체는 객체이기 때문에
<Card />
처럼 부모 컴포넌트 자체를 JSX로 사용할 수 없고,
<Card.Root>
<Card.Title />
<Card.Description />
</Card.Root>
와 같은 방식으로만 사용 가능하다.
그래서 나온 방식이 바로 정적 프로퍼티 방식이다.
부모 컴포넌트에 자식 컴포넌트를 정적 프로퍼티로 붙이는 방식이다.
1] 직접 붙이는 방식
const Card = CardRoot;
Card.Title = CardTitle;
Card.Description = CardDescription;
2] Object.assign 방식
const Card = Object.assign(CardRoot, {
Title: CardTitle,
Description: CardDescription,
});
=> 여러 개를 한 번에 붙일 수 있는 방식이다.
위 방식대로 작성하면,
부모 컴포넌트는 부모 컴포넌트 대로 단독 사용 가능하고,
자식 컴포넌트는 부모 컴포넌트와 공유되는 Compound Component방식으로 사용할 수 있게 된다.