가끔 리액트로 컴포넌트를 만들다보면 이렇게 까지 나눠야해? 이런 느낌도 들기도 하고 얼마나 나눠야하지 라는 생각이 들기도 하다. 그래서 합성 컴포넌트에 대해서 알아보자
합성 컴포넌트란?
하나의 컴포넌트를 여러 가지 집합체로 분리한 뒤, 분리된 각 컴포넌트를 사용하는 쪽에서 조합해 사용하는 컴포넌트 패턴을 의미한다.
(우선 아래와 같은 다이얼 로그가 있다. 디자인은 무시 바랍니다.)
코드는 이러하다.
type Props = {
isOpen: boolean;
title: string;
buttonLabel: string;
};
export default function Dialog(data: Props) {
const { isOpen, title, buttonLabel } = data;
return (
<div className='dialog'>
<span className='dialog__title'>{title}</span>
<button>버튼</button>
</div>
);
}
위의 컴포넌트는 간단하게 버튼이 있고 내용을 보여주는 컴포넌트다. 여기서 만약 이 컴포넌트를 보여주고 싶을 때는 위에서 처럼 보여주고 다른 걸 추가해서 보여주고 싶을 떄는 어떻게 해야할까?(즉 기존 컴포넌트를 재사용할 수 있게 다른 다이얼 로그를 만드려면 어떻게 해야할까?)
아주 간단한 방법으로는
조건부로 해두고 해당 데이터가 존재하면 렌더링 해주는 방식으로 해줘야한다.
type Props = {
isOpen: boolean;
title: string;
buttonLabel: string;
etcList?: string[];
};
export default function Dialog(data: Props) {
const { isOpen, title, buttonLabel, etcList } = data;
return (
<div className='dialog'>
<span className='dialog__title'>{title}</span>
{etcList?.map((item) => (
<div>{item}</div>
))}
<button>버튼</button>
</div>
);
}
💡 하지만 이렇게 하다 보면 내 미래처럼 이 컴포넌트가 어떻게 변화될지 몰라서 이런식으로 계속 추가하다보면 나중에는 유지 보수가 힘들어진다.
그러면 다른 내용을 손쉽게 추가할 수 있게 합성 컴포넌트 패턴을 사용해 보자.
1. 일단 다이얼로그 컴포넌트를 서브 컴포넌트로 구현해 보자
여기서 Dialog는 장바구니라고 보면 된다. 이 장바구니에 우리는 서브 컴포넌트 들을 등록 시켜 줄 것이다
export default function Dialog(data: Props) {
const { children } = data;
return <div className='dialog'>{children}</div>;
}
등록을 어떻게 시켜주냐면 아래와 같이 그냥 Dialog.DialogLabelButton = DialogLabelButton; 이렇게 해주면 된다.
type DialogButtonProps = {
children?: ReactNode;
onClick?: (e: MouseEvent) => void;
};
function DialogLabelButton({ children }: DialogButtonProps) {
return <>{children}</>;
}
Dialog.DialogLabelButton = DialogLabelButton;
아래는 사용되는 전체 코드
import React, { Children, ReactNode } from 'react';
type Props = {
children: ReactNode;
};
export default function Dialog(data: Props) {
const { children } = data;
return <div className='dialog'>{children}</div>;
}
type DialogTitleProps = {
children?: ReactNode;
};
function DialogTitle({ children }: DialogTitleProps) {
return <div className='dialog__title'>{children}</div>;
}
Dialog.DialogTitle = DialogTitle;
type DialogButtonProps = {
children?: ReactNode;
onClick?: (e: MouseEvent) => void;
};
function DialogLabelButton({ children }: DialogButtonProps) {
return <>{children}</>;
}
Dialog.DialogLabelButton = DialogLabelButton;
type DialogStrListProps = {
children?: ReactNode;
};
function DialogStrList({ children }: DialogStrListProps) {
const childrenArray = Children.toArray(children);
return (
<>
{childrenArray.map((item) => (
<div>{item}</div>
))}
</>
);
}
Dialog.DialogStrList = DialogStrList;
2. 이제 장바구니에서 필요한 걸 골라 원하는 순서대로 원하는 것만 빼서 사용하면 된다.
아래 그림 처럼 사용을 해주면 된다. 그러면 원하는 것만 원하는 순서로 배치하여 UI를 그릴 수 있다.
참고자료: https://fe-developers.kakaoent.com/2022/220731-composition-component/