사진: Unsplash의 Tirza van Dijk
디자인 시스템이 레거시가 되는 순간
우리가 그리는 이상적인 디자인 시스템
디자인 시스템을 만들기로 했을 때, 우리는 아름다운 미래를 상상합니다. 일관된 UI/UX, 재사용 가능한 컴포넌트들, 개발 속도의 비약적 향상. 디자이너와 개발자 사이의 원활한 협업. 한 번 만들어두면 모든 팀이 행복해지는 마법 같은 것을 기대하죠.
"Button 하나 잘 만들어두면 앞으로 버튼 걱정은 없겠지?"
"Modal도 만들어두자. 옵션 몇 개 넣어두면 어디서든 쓸 수 있을 거야."
그렇게 디자인 시스템 프로젝트가 시작됩니다.
진짜 현실에서의 디자인 시스템
몇 달이 지났습니다. 처음엔 5개였던 Modal의 props가 어느새 20개를 넘어가고 있습니다. 새로운 요구사항이 들어올 때마다 props가 하나씩 추가되고, 컴포넌트 내부는 조건문으로 가득 차게 됩니다. (실제로 제가 겪었던 경험입니다.)
// 처음엔 이랬는데...
interface ModalProps {
isOpen: boolean;
onClose: () => void;
children: ReactNode;
}
// 6개월 후
interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
showHeader?: boolean;
showFooter?: boolean;
showCloseButton?: boolean;
closeButtonPosition?: 'left' | 'right';
size?: 'sm' | 'md' | 'lg';
// ... 20개 넘는 props
}
이 상태에서 디자이너가 말합니다. "이번 피처에서는 Footer에 버튼이 3개 필요해요. 그리고 Body 영역 레이아웃은 이 화면에서만 다르게 디자인해서 쓰고 싶어요."
Props 지옥에 오신 것을 환영합니다.
물론 이건 디자이너의 잘못이 아닙니다. 그들은 그들의 일을 할 뿐입니다. 세부적인 디자인은 비즈니스 요구사항에 따라 얼마든지 바뀔 수 있고, 바뀌어야만 합니다. 화면 위에 떠있는 형태와 모달이 열리고 닫히는 것과 같은 핵심 기능이 바뀌는 게 아니라면, 디자인은 자연스러운 변화를 겪을 수 있습니다. UI 구조의 변화는 자연스러운 일입니다. 문제는 그 변화를 수용하지 못하는 컴포넌트 설계에 있습니다.
앞으로 다가올 문제는 아래와 같습니다.
- 새로운 요구사항마다 props가 추가됨
- props 조합의 폭발적 증가로 테스트가 불가능해짐
- 컴포넌트 내부 조건문이 복잡해져 유지보수가 어려워짐
- 결국 "이 컴포넌트 너무 복잡해서 새로 만들자"라는 결론에 도달
어떻게 확신하냐고요? 2개의 팀에서 겪었고 주변에서도 겪는 모습들 그리고 유명한 오픈소스들이 (현재는 그렇지 않지만)실제로 그러했기 때문입니다.
Compound Pattern이 확장성 문제를 어떻게 해결하는가
Compound Pattern은 구조에 대한 책임을 컴포넌트가 아닌 사용하는 쪽에 위임하는 방식입니다.
HTML의 <select>와 <option>의 관계를 생각해보세요.
<select>
<option value="a">선택지 A</option>
<option value="b">선택지 B</option>
<option value="c">선택지 C</option>
</select>
<select>는 option1, option2, option3 같은 props를
받지 않습니다. 대신 자식으로 <option>들을 받고, 그것들과 암묵적으로 상태를 공유합니다.
이것이 Compound Pattern의 핵심입니다.
이제 Modal을 Compound Pattern으로 리팩토링해봅시다.
// Context로 상태 공유
const ModalContext = createContext<{ onClose: () => void } | null>(null);
// Root: 핵심 props만 관리
function Modal({ isOpen, onClose, children }: ModalProps) {
if (!isOpen) return null;
return (
<ModalContext.Provider value={{ onClose }}>
<div className="modal">{children}</div>
</ModalContext.Provider>
);
}
// 서브 컴포넌트들
Modal.Header = ({ children }) => <header>{children}</header>;
Modal.Body = ({ children }) => <main>{children}</main>;
Modal.Footer = ({ children }) => <footer>{children}</footer>;
Modal.CloseButton = ({ children }) => {
const { onClose } = useContext(ModalContext);
return <button onClick={onClose}>{children}</button>;
};
이제 사용하는 쪽에서 구조를 자유롭게 결정할 수 있습니다.
// 기본 구조
<Modal isOpen={isOpen} onClose={handleClose}>
<Modal.Header>제목</Modal.Header>
<Modal.Body>내용</Modal.Body>
<Modal.Footer><Modal.CloseButton>닫기</Modal.CloseButton></Modal.Footer>
</Modal>
// 구조 변경이 필요하면? props 추가 없이 그냥 바꾸면 됨
<Modal isOpen={isOpen} onClose={handleClose}>
<Modal.Body>
<p>간단한 알림</p>
<Modal.CloseButton>확인</Modal.CloseButton>
</Modal.Body>
</Modal>
Props 지옥 Modal에서는 "Footer에 버튼 3개, 가운데는 아이콘"이라는 요구사항에 새로운 props를 추가해야 했습니다. 하지만 Compound Pattern에서는 그냥 사용하는 쪽에서 원하는 대로 조합하면 됩니다. 새로운 props 없이요.
이것이 Compound Pattern의 힘입니다:
- 구조의 자유도: 사용처마다 다른 구조가 필요해도 컴포넌트 수정 불필요
- Props 최소화: Root 컴포넌트는 핵심 상태(isOpen, onClose)만 관리
- 관심사 분리: 각 서브 컴포넌트는 자신의 역할에만 집중
- 확장 용이: 새로운 서브 컴포넌트 추가가 기존 코드에 영향 없음
닭 잡는데 소 잡는 칼을 쓰지 마세요
Compound Pattern이 만능은 아닙니다. 모든 컴포넌트에 적용하면 오히려 복잡해집니다.
Compound Pattern이 적합한 경우
- 복잡한 UI 구조를 가진 컴포넌트 (Modal, Accordion, Tabs, Menu, Card)
- 자식 컴포넌트들 간에 상태 공유가 필요한 경우
- 사용처마다 구조가 유연하게 달라져야 하는 경우
Compound Pattern이 적합하지 않은 경우
- 단순한 컴포넌트 (Button, Input, Badge, Avatar)
- 구조가 항상 고정된 컴포넌트
- Props 몇 개로 충분히 커버되는 컴포넌트
// Button은 그냥 props로 충분
<Button variant="primary" loading={isLoading}>저장</Button>
Compound Pattern의 단점
- 러닝 커브: 처음 접하는 개발자에게는 Context와 서브 컴포넌트 구조가 낯설 수 있음
- 설계 시 커뮤니케이션 비용: 어떤 서브 컴포넌트를 만들지, 어떤 상태를 공유할지 팀 내 합의 필요
하지만 확장성을 챙기지 않았을 때의 비용을 생각해보세요. Props가 20개를 넘어가는 컴포넌트를 유지보수하거나, 결국 "새로 만들자"며 처음부터 다시 개발하는 비용에 비하면 충분히 감수할 만합니다.
요약
- 디자인 시스템은 Props 지옥에 빠지기 쉽습니다. 요구사항이 쌓일수록 props는 늘어나고, 컴포넌트는 복잡해집니다.
- Compound Pattern은 구조의 책임을 사용하는 쪽에 위임합니다. 덕분에 새로운 요구사항에도 컴포넌트 수정 없이 대응할 수 있습니다.
- 복잡한 UI 컴포넌트에 적합합니다. Modal, Accordion, Tabs 같이 구조가 유연해야 하는 컴포넌트에 사용하세요.
- 모든 곳에 적용하지 마세요. 단순한 컴포넌트는 그냥 props가 낫습니다.
디자인 시스템의 수명은 확장성에 달려있습니다. Compound Pattern을 잘 활용하면, "이 컴포넌트 복잡해서 새로 만들자"라는 말 대신 "그냥 조합만 바꾸면 되네"라는 말을 듣게 됩니다.