-
Notifications
You must be signed in to change notification settings - Fork 0
합성 컴포넌트에 스토리북 한 스푼 🥄
본격적인 기능 개발에 앞서, 프론트엔드 파트에서는 페이지 전체적으로 재사용되는 공통 컴포넌트들을 먼저 구현하기로 하였다.
작성자 본인은 오늘 아티클을 작성하게 된 문제의 컴포넌트, '옵션 카드'와 '팝업창' 구현을 맡게 되었다.
사진에서도 확인할 수 있듯이 해당 컴포넌트들은 내부 컨텐츠가 매우 유동적이게 변한다.
특히 옵션 카드의 경우 사진 상에는 나와있지 않지만 스텝마다 보여지는 컨텐츠가 다르고, 선택 옵션 내에서의 컨텐츠도 다르다.
유동적이면서도 페이지 전체에 걸쳐 재사용되는 컴포넌트들이라 어떻게 하면 깔끔하게 구현할 수 있을지 막막했다. 간단하게는 Props로 로직을 나누어 구성할 수 있었지만, 너무 많은 것들을 다루어줘야해서 코드가 깔끔하지 않을 것 같았기 때문에 해당 방법은 가장 최후의 수단으로 사용해야했다. 게다가 나중엔 더 다양한 레이아웃이 내부에 배치될 수 있는 가능성이 있었기 때문에 높은 재사용성을 위해서는 다른 방법을 찾아야만 했다.
그러다 이전에 팀원분께서 트림 선택 카드를 구현하며 사용하셨던 합성 컴포넌트 방식이 생각났다.
위에서 볼 수 있듯이 가이드 모드와 다른 트림 카드들은 구성 내용이 다르다. 처음에는 boolean type의 guide mode props를 통해 로직을 나누어 구현했지만, 합성 컴포넌트 방식을 적용하면서 코드가 매우 간결해진 것을 pull-request-#64 를 보면 알 수 있다.
여기서 옵션 카드&팝업창과의 공통점을 찾자면 틀은 그대로 가져가지만 내부 컨텐츠가 유동적으로 바뀐다는 점이 작성자가 맡은 컴포넌트의 특성과 정확히 일치한다. 이에 따라, 처음 써보는 구성 방식이지만 작성자가 맡은 컴포넌트에도 적용해보기로 결심했다.
먼저 합성 컴포넌트가 무엇인지에 대해 알아보자. 말로만 설명하면 이해하기 쉽지 않으니 작성자 본인이 직접 만든 컴포넌트를 예시로 들어 설명해보도록 하겠다.
합성 컴포넌트에 대해 설명하기에 앞서, 아쉽게도 작성자가 구현을 맡았던 옵션 카드의 경우는 더 이상 합성 컴포넌트의 구조를 띠고 있지 않음을 알린다. 데이터셋을 수정하고 셀프/가이드 모드 페이지를 구성하며 props를 사용하는 것이 더 낫다고 판단하여 해체하였다. 이와 같은 경우에서 확인할 수 있듯이 합성 컴포넌트가 항상 최적의 선택지는 아니다. 합성 컴포넌트가 괜히 구조를 더 복잡하게 만드는 경우도 있으니 상황에 맞춰 사용하는 것이 가장 좋다. 따라서 옵션 카드 대신 팝업창을 예시로 들어 설명하도록 하겠다.
먼저 합성 컴포넌트란 '하나의 컴포넌트를 여러 집합체로 분리한 후, 분리된 각 컴포넌트를 사용하는 쪽에서 조합해 사용하는 패턴'이다.
영차 팀 기획에 있는 팝업창의 경우에는 합성 컴포넌트를 적용하기 아주 좋은 예시에 해당한다. 다양한 타입의 팝업들이 틀과 로직을 동일하게 가져가지만, 내부 컨텐츠만 다르게 배치되어 있기 때문이다. 재사용성을 최대로 높이기 위해서는 합성 컴포넌트가 최적의 선택지이다.
따라서 지금부터 우리는 팝업창 내부 컨텐츠를 여러 하위 컴포넌트로 분리한 뒤, 이를 사용하는 쪽에서 조합하여 사용할 것이다.
먼저 팝업 컴포넌트의 디렉토리 구조를 한번 살펴보자.
PopUp
├─ dir) Contents
│ ├─ CenteredDescription.tsx
│ ├─ DualMufflerImg.tsx
│ └─ ModelCarousel.tsx
│ ・
│ ・
├─ dir) PopUpMain
│ └─ index.tsx
├─ constant.ts
├─ index.stories.tsx
└─ index.tsx
PopUpMain이 상단의 빨간색 박스, Contents가 하단의 파란색 박스에 해당한다.
Contents는 내부에서 유동적으로 변경되는 컨텐츠에 해당하는 하위 컴포넌트들을 담아놓은 디렉토리이고, PopUpMain도 하위 컴포넌트에 해당하지만 팝업 내에서 고정적으로 재사용되는 컴포넌트에 해당하기 때문에 다른 디렉토리로 빼놓았다. 가장 바깥의 index.tsx는 팝업창의 메인 컴포넌트에 해당한다.
합성 컴포넌트를 구성할 때는 메인 컴포넌트의 index.tsx에서 하위 컴포넌트들을 {children}으로 받도록 코드를 작성해주면 된다.
[index.tsx]
<div className="main-component">
{children}
</div>
그리고,
export default Object.assign(PopUp, {
PopUpMain,
CenteredDescription,
PopUpButton,
DualMufflerImg,
ModeSelectCard,
ModelSelectCard,
ModelCarousel,
PopUpProgressBar,
});
Object.assign() 메서드를 사용하여 새로운 객체를 생성해주면 끝이다. 이는 PopUp 메인 컴포넌트와 하위 컴포넌트들을 하나의 객체로 묶어서 내보내는 과정이다.
이렇게 합성 컴포넌트로 팝업 컴포넌트를 구성했더니 페이지 어디에서든 자유롭게 조합을 짠 뒤 팝업창을 연결할 수 있었다.
결론적으로, 합성 컴포넌트는 요구사항이 복잡하고 더 다양한 상황을 고려해야 하는 상황에서 재사용성과 유연성을 높여야할 때 적합하게 사용할 수 있다.
코드를 자세히 보고싶다면 pull-request-#133을 참고하면 된다.
이번 프론트엔드 팀의 사용 기술 스택 중 특이한 사항이 있다면 스토리북을 사용한 것이라고 말할 수 있다.
스토리북은 UI 컴포넌트를 독립적인 환경에서 그려볼 수 있는 툴이다.
스토리북의 대략적인 장점은 다음과 같다.
- 결과물을 페이지에서 분리된 환경에서 쉽고 빠르게 확인할 수 있다.
- 최초 개발자뿐만 아니라 코드리뷰나 수정을 하는 다른 개발자나 디자이너도 쉽게 확인할 수 있다.
- 환경이나 다른 컴포넌트와 의존성이 낮아야 독립적인 환경에서 그릴 수 있기 때문에, 더 나은 설계를 고민하게 된다.
그렇다면 합성 컴포넌트와 스토리북의 궁합은 어떨까? 한마디로, 매우 좋다! 합성 컴포넌트의 경우에는 다양한 레이아웃 배치를 확인해야하기 때문에 당장 앱 단에서 확인을 하기가 번거롭다. 따라서 스토리 파일을 만들어 각각의 레이아웃 배치에 따른 스토리를 나누어 확인하면 편하게 UI 테스트를 진행할 수 있다.
export const ModelChange: Story = {
args: {
children: (
<PopUp onClose={closePopUp}>
<PopUp.PopUpMain
title={ModelChangePopUpText.title}
imgSrc={<ModelChangePopUpText.ImgSrc />}
/>
<PopUp.CenteredDescription
description={ModelChangePopUpText.description}
/>
<PopUp.PopUpButton
greyButtonContent={ModelChangePopUpText.greyButtonContent}
blueButtonContent={ModelChangePopUpText.blueButtonContent}
/>
</PopUp>
),
},
};
스토리북의 추가적인 장점을 하나 말하자면, 위와 같이 사용법을 남겨놓기 때문에 다른 개발자가 봤을 때도 컴포넌트 사용법을 직관적으로 이해할 수 있다는 장점이 존재한다.
- FE - 나도 오픈소스 개발자? (NPM 배포기)
- FE - 합성 컴포넌트에 스토리북 한 스푼 🥄
- FE - Tailwind CSS 찐하게 사용해보기
- AOS - 안드로이드 네트워크 연결
- AOS - API 요청에 따른 동적 탭 생성
- AOS - 나도 오픈소스 개발자? (jitpack 배포기)
- AOS - 폭죽 애니메이션
- AOS - 가이드 모드 애니메이션
- AOS - 뷰모델과 애니메이션을 같이 사용했을때의 ISSUE
- BE - 무중단 배포에 대해 알아보자!
- BE - 더 무중단스러운 배포를 위한 graceful shutdown
- BE - 쿼리 최적화에 대해 알아보자!
- BE - 실전, 쿼리 가속도 업!