티스토리 뷰

반응형

Frontend에서 컴포넌트를 만들기 위해 우리가 가장 먼저 접하는 개념은 컴포넌트 주도 개발(Component Driven Development)이라고 할 수 있다. 컴포넌트 주도 개발(Component Driven Development)이란 컴포넌트를 모듈 단위로 개발하여 사용자 인터페이스를 만드는 개발 및 설계 방법론이다. 컴포넌트란, 상호 교황이 가능하고 표준화된 UI 구성 요소라고 할 수 있다. 우리는 리액트를 통해 작은 컴포넌트를 만들고, 그 컴포넌트를 활용해 개발을 진행한다. 컴포넌트 주도 개발(CDD)란 재사용 가능한 컴포넌트 만든 후에, 전체 화면(View)을 구성하기 위해 결합해 가는 상향적 구조(bottom-up)라고도 할 수 있다.

 

UI는 프로젝트가 커질 수록 관리하기가 어렵다. 왜냐하면 크기가 커질수록 로직이 복잡해지기 때문에, 작은 수정에도 UI 자체가 깨지기 쉽고 디버깅이 어렵다. 이러한 문제는 UI 구성요소를 모듈 식으로 세분화하여 유연한 컴포넌트를 만든다면 해결가능하다. CDD의 장점을 정리해 보면 아래와 같다.

 

  1. 독립적으로 컴포넌트를 분리하여, 다양한 시나리오에서 작동하는지 확인 할 수 있다.
  2. 컴포넌트 자체 테스트가 가능하다.
  3. 재사용이 쉬워 UI를 빠르게 작성이 가능하다.
  4. 컴포넌트를 공유하면 개발 및 디자인이 동시에 가능하다.

이러한 이유로 CDD를 통해 개발을 진행한다. CDD의 여러 방법 중에 Atomic Design을 알아보고 어떠한 컴포넌트 설계가 우리 개발에 필요한지 고민하는 시간을 가져보자.

 


 

아토믹 디자인(Atomic design)


컴포넌트 설계에 정답이 있다고 할 수는 없다. 개발자 각각이 의미있다고 생각되는 기준으로 컴포넌트를 나누기 때문에 매우 주관적이다. 이러한 이유로 재사용이 어려운 컴포넌트가 만들어질 수 있다. 이는 컴포넌트의 관심사가 많아지고 코드가 복잡해지므로 좋은 구조를 가진 컴포넌트를 만들기가 어려워진다.

 

 

 

이러한 문제는 아토믹 디자인을 통해 어느정도 해결 가능하다. 아토믹 디자인은 이름에서 알 수 있듯이, 모든 것은 atom(원자 컴포넌트)로 구성되어 있고, atom(원자)가 모여 molecule(분자)가 되고, 분자가 모여 organism(유기체)가 되고, 그 후에 원하는 물질이 된다. 이러한 방식으로 각각에 역할을 주고 UI를 구성해 나가는 방식이 Atomic Design이라고 한다. 이를 컴포넌트에 적용해 보면 atom > molecule > organism > template > page로 적용된다고 할 수 있다.

 

  • atom (원자)
  • molcules (분자)
  • organism (유기체)
  • template (템플릿)
  • page (페이지)

 

atom은 더 이상 분해할 수 없는 기본 컴포넌드라면, input / button 등등 과 같은 기본 HTML element나 글꼴 Layout들이 포함되는 것을 알 수 있다. 폰트나 색상이 아닌 여러 속성들도 더 이상 쪼갤 수 없다면 Atom에 포함된다고 할 수 있다. Molecule은 Atom이 여러 개의 Atom이 모여 목적성이 있는 하나의 컴포넌트가 된다. Molecule은 재사용이 많이 되는 구조를 가지며, 단일 책임 원칙을 고려하며 만들어야 한다. form을 이루는 element들은 molecule라고 할 수 있고, 이러한 molecule이 모여 navbar를 이루면 organism이라고 할 수 있다. 그렇다면 atom들이 모여 molecule과 organism이 된다는 말인데, 이게 실제 프로젝트를 만들다 보면, 특정한 기준으로 딱 나누어 atom 컴포넌트를 몇 개 모아서 molecule이 된다고 정의하기는 어렵다. 왜냐하면 아토믹 다지인 단위를 나누는 것 자체가 주관적이기 때문이다. 따라서 지금부터의 글은 주관적인 의견이 다분히 반영된다. 참고만 하여 각자의 프로젝트에 맞게 더 좋은 방법을 사용하도록 하자.

 

molecule는 단일 책임 원칙에 따라 하나의 역할을 한다. 그리고 origanism은 여러 개의 atom, molecule, organism으로 구성되어 Layout 기준으로 나누는 영역을 가진다. 이때 기본 원칙은 상태값이 포함되면 origanism으로 판단하고, UI만 존재하여 data를 받아서 작성되는 컴포넌트라면 molecule로 기본 큰 분류를 하면 좋다. 따라서 naming convension를 세팅할 때, molecules는 UI 관점의 이름(Input, button, Carousel, TextBadge, ProfileImage, ListItem 등등)을 적는 게 좋고, Organism의 경우는 기능적인 이름(Comment, CommentInput, AnnouncementItem, ProfileListItem, CheckBoxListItem, ProfileListItemWithBadge 등등)의 컴포넌트 이름을 넣어준다.

 


단일 책임 원칙(SRP: Single Responsibility Principle)은 다섯 가지 SOLID 애자일 원칙 중 하나로, 하나의 컴포넌트는 하나의 책임을 가져야 한다는 의미이다. 쉽게 말하면, 컴포넌트에 많은 책임이 있다면, 하나의 책임을 변경하면 개발자가 모르는 사이에 다른 컴포넌트에 영향을 줘서 버그를 발생할 가능성이 커진다. 여기서 책임을 모듈이나 기능으로 생각하면 조금 더 쉽게 이해가 될 수 있다.

 

정리하면, 단순한 UI를 표시하는 하나의 역할을 가진다면, Molecule로 판단하고, 기능적 요구사항이 포함된다면 Organism이라고 생각하면 좋다. 시간을 보여주는 UI는 Molecule로 판단을 하고, 실시간 시간이나 외국 시간을 보여주는 요구사항이 포함된 컴포넌트는 Organism으로 판단한다. Organism과 Template를 판단하는 기준은 재사용으로 판단한다. 재사용이 가능하다면 Template(Ex. WorkspackeListTemplate, ContentTemplate 등)으로 판단하고, 재사용이 필요하지 않은 단일 컴포넌트라면 Organism으로 판단한다.

 

organism 제작 후 내부 요소를 추가 수정/추가해야 할 일이 있을 수 있다. 예를 들어 추가 요구사항으로 프로필 컴포넌트에 전화번호를 추가해 달라는 요청이 있었다. 이러한 경우에 새로운 컴포넌트를 만들어야 할까? 기존의 컴포넌트를 사용할 수 없을까? 이럴 때 사용하는 것이 바로 Compound Component이다. 관련 내용은 dropdown component에서 추후 추가로 작성을 해 보도록 하겠다.

 

추가로 무엇으로 쪼개어 재사용하여 나누는지 집중하는 것도 중요하지만, 사실 아래와 같이 table의 위치기준으로 나누어 Props를 줄이는 방법도 좋다. 왜냐하면 변수가 많아진다는 것은 예상하지 못하는 결과를 이끌어 내기도 하기 때문이다.

 

<Table
  top={}
  bottom={}
>
  ...body  
</Table>

 


 

Side Effect 핸들링

Side Effect는 외부의 영향으로 상태가 변경되는지로 판단을 한다. 우리가 주로 사용하는 부분은 네트워크 통신(API)에서 주로 사용한다. 이는 Atom Molecule Oranism Template 내부에 포함시키지 않는다. 이러한 이유는 외부의 요인에 의해 컴포넌트 자체 에러를 발생할 가능성이 있기 때문이다. 따라서 Page 내부에서 Side Effect(API)를 처리하한다. 하지만 이러한 경우에는 Page 자체에 너무 많은 책임을 가지게 된다. 이러한 경우 Wrapped라는 레이어를 하나 더 두고, Side Effect를 포함시켜 주는 것도 좋은 방법이 될 수 있다. 이러한 많은 layer들이 있다면, props로 많은 컴포넌트로 전달이 필요하다. 그러면, Redux 나 Context API를 통해 Wrapped에서 관리하는 방식도 좋은 방식으로 판단된다.

 

또한, 사용자 상요작용에 의한 컴포넌트는 Side Effect가 발생하는 지점에서, 넣어주고 의존성이 역전되지 않도록 한다. 쉽게 말하면 model, tooltip 같은 UI는 내부 컴포넌트에서 직접 핸들링하지 않고 상위 컴포넌트에서 아래와 같이 핸들링해 준다.

 

const Wrapped = () => {
  const [visible, setVisible] = useState(false);

  const ModalComponent = ({ visible }) => (
    <Modal visible={visible}>
      <Content />
    </Modal>
  );

  const handleClick = () => {
    setVisible(!visible);
  };

  return (
    <div>
      <Component onClick={handleClick} modal={<ModalComponent visible={visible} />} />
    </div>
  );
};

 

 


 

 

물론 Atomic Design의 원칙을 따라도 되지만, 프로젝트 상황에 따라 적절히 변경하는 것도 좋다. 예를 들면, 필자는 기존의 다른 프로젝트들의 통일성과 복잡한 UI가 없는 프로젝트에서는 아래와 같이 분류하여 컴포넌트를 사용하였다.

 

Core Component : Atomic Design의 Atom과 동일하다.

Wrapper Component : Core Component를 결합하여 만든 UI Component

Layout Component : 화면 Layout을 담당하는 Component(navbar, LNB 등)

Page Component : 전체 페이지를 담당하는 Component

 

 

그렇다면 Atomic Design은 항상 옳은가? 기본적으로 소프트웨어는 끊임없이 변화한다. 따라서 변경에 유연하게 대응할 수 있는 컴포넌트를 만들어야 한다. 5단계로 나누어 사용하는 Atomic이 항상 옳다고는 할 수 없다. 그렇다고 상황에 따라 정답이 있는 것도 아니다. 동료들과 고민하면서 프로젝트에 맞게 변경에 유연하게 대응 가능한 컴포넌트 설계를 할 수 있도록 고민해 보자.

 

 

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함