주니어의 Atomic design & Storybook 도전기

영근·2023년 3월 5일
2

새 프로젝트는 힘들지만 계속 진행되어 가는 걸 보면 흐뭇하다 😙

# 배경

드디어 시작된 새 프로젝트 !
우리 회사 프로덕트는 모바일용으로 개발되어 있어, PC에서 접속하면 양 옆에 여백이 있어 답답한 느낌이 있다.
그래서 PC용 프로덕트 개발이 숙원사업이었고, 그 프로젝트를 이번에 시작하게 되었다.

같이 프로젝트를 진행하시는 개발자 슨배님께서 Next.js로 프로젝트 세팅을 해주셨고, Atomic design으로 컴포넌트를 만들어 개발을 진행해보자고 하셨다. Atomic design은 공부할 때 글 몇 개를 읽어본게 다였고, 지금까지 디자인 패턴을 본격적으로 사용해본 적이 없었기 때문에 나도 좋은 기회가 될 거라고 생각하고 바로 공부를 시작했다.

Storybook이 모바일 버전 프로덕트에서는 거의 사용되지 않았기 때문에, Storybook도 적극 활용해보자고 하셨다. 나도 요새 Storybook을 활용한 UI 문서화에 관심이 많았기 때문에 .. 개이득이라고 생각했다 🥹

현재 한참 진행중이지만, 이번 프로젝트에서 글의 주제인 Atomic designStorybook 외에도 많은 것들을 배울 수 있을 거라고 생각해서 개인적으로 많이 기대하고 있다.


# 왜 Atomic design인가 ?

현재 모바일용 프로덕트의 파일 구조(컴포넌트 부분)는 대강 이렇게 되어있다.
(발글씨 주의)

src 폴더

  • global하게 사용되는 컴포넌트들이 모여있는 components 폴더
  • 기능별 폴더가 들어있는 features 폴더
  • 전체 라우트를 관리하는 Routes 폴더

features 폴더

  • 기능별(Home, Map, Listing(거래) 등) 폴더

기능별 폴더

  • 그 기능과 관련된 컴포넌트들이 모여있는 components 폴더
  • 그 기능과 관련된 라우트를 관리하는 Routes 폴더
  • 그 기능과 관련된 훅을 관리하는 Hooks 폴더 등

요런식의 파일 구조를 가지고 있는데, 이렇게 하면 기능적으로 구분되어 관련 코드를 찾기 쉽다는 장점이 있다.
하지만 글로벌하게 사용될 수 있는 여러 컴포넌트나 코드들이 여기저기 산개해있어 코드 중복이 많다는 단점이 있었다.

그래서 컴포넌트를 좀 더 재사용하고, 중복 코드를 방지하고, 나아가서 최소 단위 Atom 컴포넌트르 적극 활용하여 좀 더 빠르게 개발을 하자는 목적에서 Atomic design을 선택하게 되었다.


# Atomic design이란 ?

원문을 읽으며 공부합시다 💁

brad frost가 화학에서 영감을 받은 디자인 패턴이다.
(impressive한 수염을 가진 화학 선생님에게 배웠다고 한다🤔)

이 세상의 모든 것들은 더 이상 쪼개지지 않는 Atom으로 이루어져 있고, 그 Atom들이 모여 molecule이 되고, 다시 그 molecule들이 모여 organism이 되는데, 이러한 사고로 UI 개발을 이해하는 것이다.

주의할 점은 Atomic design 은 linear한 과정이 아니라, UI를 바라보는 하나의 mental model이라는 점이다.

1. Atom

말 그대로 더 이상 쪼개지지 않는 원자이다.
HTML의 기본 요소들을 생각하면 된다.(<label>, <input>, <button> 등)

개인적으로 개발을 하면서, 디자이너님이 만들어주신 basic한 디자인 가이드를 참고해서 껍데기를 먼저 만들고, 이후 커스터마이징과 기능을 생각한 props를 만들어주는 식으로 개발했다.

2. Molecule

여러 원자가 모여 만들어진 분자이다.
예를 들어 Atom으로 inputbutton을 만들었다면, 그 두 가지를 합쳐 검색 버튼이 있는 SearchInput 컴포넌트를 쉽게 만들 수 있다.

3. Organism

여러 개의 원자/분자가 모인 유기체이다.
예를 들어 Atom으로 만든 button과, molecule로 만든 SearchInput 를 통해 Header 컴포넌트를 쉽게 만들 수 있다 !
이 때 Organism component는 인터페이스의 한 섹션의 스케일로, 계속해서 사용될 가능성이 큰 form이나 table 등을 예로 들 수 있다.

4. Template

Now, friends, it’s time to say goodbye to our chemistry analogy.

앞서 화학적 접근으로 만들었던 여러 UI 요소들을 웹페이지에 적용하는 단계이다.
안에 콘텐츠/데이터가 없는 페이지의 skeleton 역할이다.

5. Page

Template에 여러 데이터와 콘텐츠를 넣으면 페이지 완성 !


# Atom을 실제로 만들어 봅시다👩‍💻

우선 가장 기본이 되는 Button 컴포넌트를 Atom 으로 만들었다.
그 과정에서는 역시 벨로그의 주인장님 킹갓 벨로퍼트님의 글을 많이 참고했다.
(복 받으세요 ..)

스타일 먼저 만들기

먼저 디자이너님께서 만들어주신 버튼 디자인 가이드를 보고, 껍데기부터 만들기 시작했다.
버튼의 5가지 테마(디자이너님이 만들어주신 primary, secondary, gray, outline의 4가지와, 개발하면서 많이 사용할 것 같은 ghost)를 먼저 만들었다.

❕ 프로젝트에서는 Next.js에서 TailwindCSSEmotion을 함께 사용하기 위해 twin.macro를 사용하고 있다.

//  theme별 스타일은 object로 관리한 뒤,
const themes = {
  primary: tw`text-white bg-gray-1000 hover:bg-gray-800 disabled:bg-gray-400 disabled:text-white`,
  secondary: tw`text-white bg-nego-800 hover:bg-nego-600 disabled:bg-nego-300 disabled:text-white`,
  gray: tw`text-gray-1000 bg-gray-200 hover:bg-gray-400 disabled:bg-gray-200`,
  outlined: tw`bg-white border-gray-300 border-l border-r border-t border-b text-gray-1000 hover:border-gray-1000 hover:bg-white disabled:text-gray-500 disabled:border-gray-500`,
  ghost: tw``,
};

...

<button
      type="button"
      css={[
        defaultStyle,
        themes[theme], // 요렇게 props로 전달받은 theme을 key로 꺼내준다.
      ]}
      onClick={onClick}
      disabled={disabled}
    >
      {children}
</button>

이렇게 theme 이라는 props에 따른 스타일을 만들었다 !

이제 어떤 props가 필요할 지 생각해야 하는 단계이다.
먼저 기능 말고 스타일 관련을 생각해보면,

버튼이라면 disabled될 수도 있고, selected 될 수도 있고.. loading 상태일 때도 있으니까 .. 아 size도 필요하겠네 ..

대충 요런식으로 생각이 흘러가는데, 이 때쯤 레퍼런스를 보고싶다 ! 는 생각이 간절하게 든다.
벨로퍼트님의 글을 보면 버튼은 잘 나와있지만, 다른 컴포넌트를 만들때는 어쩌나,, 싶을 때 우리의 킹갓 Storybook 형님들이 도움을 주신다.

요렇게 Storybook에서 다른 프로젝트에서는 어떻게 컴포넌트들을 만들어 관리하고 있는지, 버튼들에서 어떤 props를 사용하고 있는지 예시가 쫙 뜬다 🥹
(실제로 나도 여기서 예시들을 보면서 디자인가이드에 없는 ghost 테마를 추가했다.)

이렇게 스타일을 추가하고, 커스터마이징 가능성까지 고려해서 props 만들기 성공!

기능 추가하기

자고로 버튼이란 onClick 이벤트를 위해 존재하는 것이기 때문에..! onClick 관련한 코드도 추가해주고, 버튼 안에 텍스트/아이콘 등을 자유롭게 넣기 위해 안에 들어가는 콘텐츠는 ReactNode 타입 children을 넘겨주면 끝 !

그래서 props의 타입은 이렇게 되었다.

export type ButtonProps = {
  /** 버튼 안의 내용 */
  children?: string | ReactNode;
  /** 클릭 했을 때 호출할 함수 */
  onClick?: (e?: React.MouseEvent<HTMLButtonElement>) => void;
  /** 버튼 테마 */
  theme?: 'primary' | 'outlined' | 'ghost' | 'secondary' | 'gray';
  /** 버튼 사이즈 */
  size?: 'default' | 'small' | 'big' | 'medium' | 'none';
  /** 버튼 비활성화 */
  disabled?: boolean;
  /** 커스텀 스타일 */
  custom?: TwStyle;  // twin.macro에서 사용하는 type
  /** 로딩 여부 */
  isLoading?: boolean;
  /** 선택되었는지 여부 */
  isSelected?: boolean;
};

스토리북에서 확인하기

여기까지 버튼을 잘 만들었다면, 스토리북에서 각 props에 따라 어떻게 버튼이 변하는지 한 눈에 확인할 수 있게 코드를 작성해준다.

import type { ComponentMeta, ComponentStory } from '@storybook/react';
import User from '@/assets/icons/user.svg';
import tw from 'twin.macro';

import Button from '.'; 

export default {
  title: 'atoms/Button',
  component: Button,
} as ComponentMeta<typeof Button>;

export const Default: ComponentStory<typeof Button> = (args) => (
  <Button {...args}>Button</Button>
);

Default.args = {
  theme: 'primary',
  size: 'default',
};

// 테마 관련
export const Theme = () => (
  <div tw="flex gap-2">
    <Button theme="primary">Primary</Button>
    <Button theme="secondary">Secondary</Button>
    <Button theme="gray">Gray</Button>
    <Button theme="outlined">Outlined</Button>
    <Button theme="ghost">Ghost</Button>
  </div>
);

// 아이콘이 들어간 버튼
export const IconButton = () => (
  <div tw="flex gap-1">
    <Button theme="outlined">
      <User />
    </Button>
    <Button theme="primary">
      <User />
    </Button>
    <Button theme="ghost">
      <User />
    </Button>
  </div>
);

요렇게 index.stories.tsx 파일을 작성해주고 yarn storybook 커맨드로 실행해주면 ?

요렇게 바로바로 props 에 따른 버튼 변화를 확인할 수 있고,

index.stories.tsx 하단에 작성한 코드처럼 바로 확인이 가능하게 story가 생성된다.
이렇게 Atom 컴포넌트인 Button 완성 !


# Molecule도 만들어 봅시다 👩‍💻

위 버튼처럼 정성스럽게(?) Atom 컴포넌트를 만들었다면, 그 컴포넌트를 활용해서 정말 빠르게 Molecule 컴포넌트를 만들 수 있다.

예시로 Atom 컴포넌트인 InputButton 컴포넌트를 활용해서 SearchInput 컴포넌트를 아래와 같이 만들었다 !
(만드는 과정은 버튼 컴포넌트와 동일하기 때문에 생략)

<Input
     value={inputValue || ''}
     divStyle={tw`w-[23.75rem] flex justify-between`}
     placeholder="주소 또는 단지명을 입력하세요"
     onFocus={() => setFocused(true)}
     onChange={(e) => {
       setInputValue(e.currentTarget.value);
       onChange(e);
     }}
   >
     <div tw="flex items-center gap-4">
       <Button
         theme="secondary"
         custom={tw`h-[2.25rem] px-[0.625rem]`}
         onClick={onClickButton}
       > // 위에서 만든 버튼 컴포넌트 사용 !
         <Search /> // 아이콘을 children으로 넣어줌
       </Button>
     </div>
   </Input>

스토리북에서는 이렇게 확인할 수 있다.


# Molecule과 Organism의 모호함

한창 요렇게 신나게 개발을 하고 있는데, 슨배님께서 나에게 한 가지 질문을 해주셨다.

Molecule 과 Organism의 경계가 모호하지 않나요 ?

듣고 보니까 이론적으로는 구분이 되어 있지만, 실무적으로 둘을 구분하기가 어려웠다. 어쨌든 둘 다 Atom이 모여 하나의 컴포넌트가 되는 건데, 어떤 기준으로 구분해야 할까?

그렇게 슨배님과 함께 고민해 본 결과, 우리는 '글로벌하게 사용되는가'를 기준으로,
글로벌한 컴포넌트는 Molecule 로, 특정 기능 혹은 페이지에만 사용되는 컴포넌트나 재사용성이 떨어지는 컴포넌트는 Organism으로 관리하기로 했다.

결과적으로 Molecule 컴포넌트로 위에서 만들었던 MapSearchInput 은 이름에서 부터 지도에서만 쓰는 컴포넌트이기 때문에, Organism으로 이동했다.


# 사용해 본 후기

1. 빠른 코드 파악과 테스트

가장 큰 장점은 작게 나뉜 컴포넌트를 Storybook으로 빠르게 파악할 수 있다는 점이다.

사실 일정에 치여서 슨배님께 내 코드 리뷰를 부탁드리거나 하기가 어려웠는데, 이번 프로젝트를 진행하면서는 내가 컴포넌트를 하나씩 만들면 스토리북에서 빠르게 확인해주시고, 이런 저런 조언이나 더 나은 방향을 제시해주시곤 한다.

그리고 나도 슨배님께서 만들어두신 컴포넌트를 빠르게 파악하고, 코드를 보면서 (감탄을 먼저 한 뒤에) 컴포넌트를 만들면서 어떤 점들을 고민해야 하는지 정말 빠르게 배울 수 있었다.

특히 나같은 주니어에게 Atomic designStorybook을 같이 사용하며 배우는 것은 정말 큰 행운이라는 생각이 들 정도로, 매일 매일 많은 것들을 배우고 있다.

2. 신중함

Atomic designStorybook을 사용하면서, 컴포넌트를 만들 때 더 신중하게 고민하게 되었다.
어떤 props를 이용해야 재사용성이 높고, 사이드 이펙트에서 안전하며, 커스터마이징이 자유로운 컴포넌트를 만들 수 있을 지 고민하게 되었다. 그리고 그 과정에서 여러 레퍼런스를 찾아보며 느끼는 것들도 확실히 많아졌다.

3. 빠른 UI 개발과 안심(?)

Atom 컴포넌트를 만들어두면, 그걸 이용해서 다른 컴포넌트를 만들 때 정말 빠르게 개발할 수 있었다.
처음 Atom 컴포넌트를 개발하면서는, 여러 고민하는 시간때문에 더 오래걸린다는 느낌을 받았는데, 그 이후부터는 정말 빠르게 여러 컴포넌트를 만들 수 있어 결과적으로 개발에 걸리는 시간이 확연히 줄어들었다.

시간이 줄어든 것뿐만 아니라, 코드가 짜임새있고 안정적이라는 느낌을 많이 받아서 개발 후에도 이전보다 살짝 안심(?)된다.
요건 아무래도 컴포넌트 별로 Storybook에서 테스트를 한 뒤에 개발하기 때문에 받는 느낌일 것 같다.

그렇다면 단점은?

지금까지 사용해 본 결과, 나는 딱히 단점이랄 것을 느껴본 적이 없다.ㅋㅋㅋ


그러니까 개발자님들 이거 외않써 ? 🤔


# Reference

🎯 https://atomicdesign.bradfrost.com/chapter-2/

https://velog.io/@velopert/create-your-own-design-system-with-storybook

https://fe-developers.kakaoent.com/2022/220303-tailwind-tips/

https://fe-developers.kakaoent.com/2022/220609-storybookwise-component-refactoring/

https://fe-developers.kakaoent.com/2022/220505-how-page-part-use-atomic-design-system/

0개의 댓글