최근 개발 프로젝트 하나를 마쳤습니다. (https://www.sejulbook.com 많이 방문해주세요 😁)
해당 프로젝트에서 처음으로 아토믹 디자인 시스템을 도입해봤습니다.
디자인 시스템을 컴포넌트로 파편화하여 쉽게 재사용하고 확장하고 싶었기 때문입니다.
하지만 아토믹 디자인 시스템은 제가 느끼기에 꽤 모호했어요.
그리고 NextJS에서 아토믹 디자인을 사용하면서 하나의 문제가 발생하기도 했구요.
그래서 아토믹 디자인 시스템을 잘써보기 위해 제가 이행했던 것들을 공유해보겠습니다.
아토믹 디자인?
아토믹 디자인(Atomic Design)은 화학적 관점에서 영감을 얻은 디자인 시스템입니다.
모든 것은 원자(Atom)으로 이루어져 있으며, 원자들이 결합하여 분자(Molecule)이 되고, 분자가 더 복잡한 유기체(Organism)으로 결합하는 화학적 원리를 React의 컴포넌트에 도입한 것입니다.
- Atom
더이상 분해할 수 없는 컴포넌트입니다.
Label, Input, Button 등이 될 수 있겠죠.
- Molecule
여러개의 Atom을 결합하여 특정 특성을 가지는 컴포넌트입니다.
Input과 Button을 결합한 SearchBar를 예로 들 수 있습니다.
- Organism
여러개의 Atom, Molecule, Organism으로 구성되는 컴포넌트입니다.
Logo, Button, SearchBar 등으로 결합된 HeaderBar를 예로 들 수 있습니다.
- Template
Page를 만들 수 있도록 여러 개의 Atom, Molecule, Organism으로 구성할 수 있습니다.
실제 컴포넌트를 레이아웃에 배치하고 구조를 잡는 와이어 프레임, 즉 실제 데이터가 없는 스켈레톤이라 할 수 있습니다.
- Page
template에 데이터를 주입하여 사용자가 보게되는 실제 콘텐츠를 담고 있습니다.
문제
1. Props Drilling
위에서 언급한 아토믹 디자인대로 컴포넌트를 구성하게 되면, props drilling이 발생할 위험이 생깁니다.
특히 NextJS 프레임워크에서는요.
NextJS는 pages 디렉토리에 정의한 파일이 곧 route가 되고, 해당 route 파일에서만 getStaticProps와 getServerSideProps를 사용하여 SSG 또는 SSR을 수행할 수 있습니다.
결국 Page -> Template -> Organism -> Molecule -> Atom 흐름으로 props drilling이 발생할 가능성이 더 높아집니다.
2. Molecule? Organism?
Molecule과 Organism의 구분이 저는 꽤 모호했습니다.
'Atom으로만 구성되어 있으면 Molecule이다!' 라 정의하기에는 해당 컴포넌트가 비즈니스 로직의 상태를 제어하는 경우 낮은 수준의 재사용 디자인 시스템이라 할 수 없게 됩니다.
이러한 문제들을 해결하기 위해서는 개인 또는 팀의 적절한 합의가 필수시 된다고 생각합니다.
제가 스스로와 타협한 아토믹 디자인 시스템 룰을 소개하겠습니다.
Template 재정의
Template 컴포넌트의 역할을 재정의했습니다.
Template 컴포넌트에서 Atom, Molecule, Organism 컴포넌트를 직접 불러오지 않도록 했습니다.
Template 컴포넌트는 레이아웃을 배치하는 딱 그정도의 역할만 부여한 것입니다.
아래 예시 코드를 보면 쉽게 이해할 수 있습니다.
// src/components/templates/SearchResult/index.tsx
import { ReactNode } from 'react';
import * as s from './style';
interface SearchResultTemplateProps {
pageTitle: string;
searchBar: ReactNode;
sortButton: ReactNode;
bookshelf: ReactNode;
}
const SearchResultTemplate = ({
pageTitle,
searchBar,
sortButton,
bookshelf,
}: SearchResultTemplateProps) => (
<s.Wrapper>
<s.Title>{pageTitle}</s.Title>
<s.SearchBarWrapper>
{searchBar}
{sortButton}
</s.SearchBarWrapper>
{bookshelf ? (
<s.BookReviewListWrapper>{bookshelf}</s.BookReviewListWrapper>
) : (
<s.AltText>
<span>{pageTitle}</span>에 대한 기록이 없습니다
</s.AltText>
)}
</s.Wrapper>
);
export default SearchResultTemplate;
Page 컴포넌트에서 Atom, Molecule, Organism 컴포넌트를 Props로 받아 배치합니다. 의존성을 주입하는 것과 같습니다.
따라서 특정 상태를 가지고 있지 않습니다.
Page 컴포넌트에서 Atom, Molecule, Organism 컴포넌트를 Template 컴포넌트의 Props로 전달하는 예시 코드입니다.
// src/pages/search/book.tsx
...
<SearchResultTemplate
pageTitle={`'${title}'`}
searchBar={<BookSearchBar initialValue={title} />}
sortButton={
<SortDropdown
onClickLatestButton={handleClickLatestSortButton}
onClickLikeSortButton={handleClickLikeSortButton}
/>
}
bookshelf={
!!bookReviewList.length && (
<Bookshelf
hasWriteBookReviewItem={false}
bookReviewList={bookReviewList}
onRefetch={refetchBookReviewList}
/>
)
}
/>
...
이렇게 Template의 역할을 재정의함으로써 Page -> Organism -> Molecule -> Atom 흐름으로 props drilling을 한층 해소할 수 있습니다.
그래도 아직 props drilling에 대해 개선할 여지가 있어보입니다.
Molecule과 Organism의 역할 확립
Organism 컴포넌트에게 전역 상태 및 데이터 fetching을 허용했습니다.
진정한 유기체가 된 것이죠.
그리고 Molecule 컴포넌트는 디자인 시스템 역할만 할 뿐 직접 데이터를 다루지 못하게 했습니다.
아래와 같이 컴포넌트 이름으로도 Molecule과 Organism의 역할을 확인할 수 있습니다.
이제 Molecule과 Organism의 모호함 문제를 해결했습니다.
또한 Organism 컴포넌트에게 전역 상태 및 데이터 fetching을 허용함으로써 props drilling 문제 또한 해소할 수 있었습니다.
.
.
.
아토믹 디자인 시스템 뿐만 아니라 무엇이든 개인 및 팀의 상황에 따라 합의하고 선택하는 과정은 참 중요한 것 같습니다.
아토믹 디자인 시스템을 더 잘쓰시고 계신 분이 있으시면 댓글로 공유해주시면 감사하겠습니다 🙏