서비스가 커질수록, 매번 UI Component를 직접 작성하는 일은 비효율적이고, 유지 보수성이 매우 떨어짐
그동안 styled-components를 이용해서 css 작업을 했음.
근데, components의 size는 매번 렌더링 되는 페이지에 맞춰서 직접 작성해왔는데,
새로운 component를 만들어야 할 때마다, 이전에 만들어 둔 component를 재사용하고 싶었음
근데, 최근에 괜찮은 방법을 알아서 적어보고자 함.
interface Props {
isOpen: boolean;
title: string;
buttonLabel: string;
onClickButton: (e:MouseEvent) => void;
}
function Dialog({ isOpen, title, buttonLabel, onClickButton }: Props){
if (!isOpen){
return null;
}
return React.createPortal(
<div>
<span>{title}</span>
<button onClick={onClickButton}>{buttonLabel}</button>
</div>
,document.body)
}
이런! 추가 요구사항이 발생했다!
Dialog에 체크박스나 버튼이 더 생기기도 하고, 주석이나 설명 등과 같은 부가적인 요소가 생겼다!
interface Props {
isOpen: boolean;
title: string;
buttonLabel: string;
onClickButton: (e: MouseEvent) => void;
isChecked?: boolean;
checkBoxLabel?: string;
onClickCheckBox? : (e: MouseEvent) => void;
descriptionList?: string[]
}
function Dialog({
isOpen,
title,
buttonLabel,
onClickButton,
isChecked,
checkBoxLabel,
onClickCheckBox,
descriptionList
}: Props){
if (!isOpen){
return null;
}
return React.createPortal(
<div>
<span>{title}</span>
{descriptionList && descriptionList.map(desc => <span key={desc}>{desc}</span>)}
{checkBoxLabel && <div>
<input checked={isChecked} onClick={onClickCheckBox} type="checkbox" id="dialog_checkbox">
<label for="dialog_checkbox">{checkBoxLabel}</label>
</div>}
<button onClick={onClickButton}>{buttonLabel}</button>
</div>
,document.body)
}
이 시점에서 우리는 여러 생각이 든다.
앞으로 얼마나 더 많은 요구사항이 추가될까?
버튼 하나의 추가만으로, 3개의 props가 늘어남.
interface DialogTitleProps {
children?: ReactNode;
}
function DialogTitle({children}: DialogTitleProps){
return <div css={/*DialogTitle 스타일*/}>{children}</div>
}
interface DialogLabelButtonProps {
children?: ReactNode;
onClick?: (e: MouseEvent) => void;
}
function DialogLabelButton({children}: DialogLabelButtonProps){
return <div css={/*DialogLabelButton 스타일*/}>{children}</div>
}
interface DialogMainProps {
children?: ReactNode;
isOpen: boolean;
}
function DialogMain({children, isOpen}: DialogMainProps){
if(!isOpen) {
return null;
}
return createPortal(<div>{children}</div>, document.body)
}
이러면 children으로 들어오는 서브 컴포넌트들은 순서에 따라 위 -> 아래로 배치될 듯
그리고 컴포넌트들을 분류해서 어느정도 위치를 잡아줄 수 있도록 구현
const DialogLabelButtonType = (<DialogLabelButton />).type;
function getDialogLabelButtons(children: ReactNode) {
const childrenArray = Children.toArray(children);
return childrenArray
.filter(
child => isValidElement(child) && child.type === DialogLabelButtonType,
)
.slice(0, 2);
}
interface DialogMainProps {
children?: ReactNode;
isOpen: boolean;
}
function DialogMain({children, isOpen}: DialogMainProps){
if(!isOpen) {
return null;
}
const dialogContents = getDialogContents(children);
const dialogLabelButtons = getDialogLabelButtons(children);
const dialogDimmed = getDialogDimmed(children);
return createPortal(
<div>
<div>{getDialogDimmed(children)}</div>
{dialogContents && (
<div>{dialogContents}</div>
)}
{dialogLabelButtons && (
<div>{dialogLabelButtons}</div>
)}
</div>,
document.body)
}
export const Dialog = Object.assign(DialogMain, {
Dimmed: DialogDimmed,
Title: DialogTitle,
Subtitle: DialogSubtitle,
Description: DialogDescription,
Comment: DialogComment,
CheckButton: DialogCheckButton,
CheckBox: DialogCheckBox,
TextButton: DialogTextButton,
Button: DialogButton,
LabelButton: DialogLabelButton,
Divider: DialogDivider,
});
// Usage
<Dialog>
<Dialog.Title>제목</Dialog.Title>
</Dialog>