[우아한테크코스 FE LEVEL2-2] npm 배포 & 합성 컴포넌트

Gyuhan Park·2024년 5월 20일
1

[ 학습 목표 ]

  • 재사용 가능한 모듈화된 컴포넌트(모달, 커스텀 훅)를 개발하고 npm에 배포 할 수 있다.
  • StorybookRTL 을 활용한 컴포넌트 문서화 및 테스트 시나리오를 작성할 수 있다.
  • 모듈화된 컴포넌트의 재사용성확장성 경험
  • 실제 프로젝트에서의 컴포넌트 통합 및 활용
  • 요구사항 변경에 따른 컴포넌트 리팩터링 및 개선

💭 TMI

레벨2 두 번째 미션이 마무리되었다. 이걸 쓰는 시점은 해당 미션이 끝나고 일주일 뒤인데 점점 회고가 늦어지는 것 같다. 그런데 저번주는 진짜 정신이 없었다. 페어 프로그래밍 + 테코톡 + 스터디 발표 준비 + 리팩터링을 모두 했어야해서 일주일이 삭제되었다. 그래도 다 만족스럽게 마무리되어서 홀가분하다.

해당 미션에서 가장 신기했던 부분은 npm 배포 다. 우리가 사용하는 라이브러리 모두 npm install로 설치할 수 있는데, 내 코드를 자유롭게 배포할 수 있었다. 생각보다 배포하는 과정이 간단했고, 그래서 오히려 라이브러리를 설치할 때 다운로드 수나 최근 업데이트 날짜를 잘 확인해야겠다는 생각이 들었다.

📘 배운 점 & 느낀 점

✅ npm 배포

내가 만든 모듈을 npm에 배포하는 미션이였는데 modalcustom hook 2가지의 모듈을 만들었다. modal은 storybook 까지 배포하여 모달을 사용하는 입장에서 어떤 기능을 요구하는지 쉽게 테스트할 수 있다. 모듈을 만들면서 해당 모듈을 사용자가 어떻게 사용할 지, 어떤 기능까지 제공해야할 지를 많이 고민하게 되었다. 또한 인터페이스만으로 해당 모듈의 의미를 잘 나타내도록 적절한 네이밍, 주석, 명세 등 여러 방법을 적용해보았다.

라이브러리를 배포하면서 package.json, tsconfig, vite.config 등의 옵션들이 어떻게 동작하는지 알게 되었다. 아직 다 알지 못하지만 그냥 사용하던 속성들에 대해 궁금증이 생겨서 추후에 tsconfig를 좀더 살펴볼 예정이다. tsconfig의 include에 포함된 모듈만 배포되고, 기본적으로 자바스크립트로 배포되어 타입 정의도 포함해야 한다.

✅ 합성 컴포넌트(컴포지션 패턴)

모달 모듈을 만들면서 합성 컴포넌트 를 도입하게 되었다. 합성 컴포넌트는 props로 처리하는 로직과 구분지어 생각할 수 있는데, 이를 도입한 이유는 prop을 받아 처리하는 컴포넌트 특성상 각 컴포넌트의 위치는 구현 시점에 결정된다. 단순한 상황별 분기에 따라 컴포넌트는 또 prop을 추가해야 하고, 구현 시점에 모든 상황을 고려해서 구현해야한다. 요구사항이 많아질 경우, 오류가 발생할 확률이 높아지며, 유지보수하기 어려워진다.

합성 컴포넌트란 하나의 컴포넌트를 분리한 뒤, 분리된 각 컴포넌트를 사용하는 쪽에서 조합해 사용하는 컴포넌트 패턴 이다.
사용하는 곳에서 컴포넌트의 조합을 활용할 수 있다면 높은 재사용성과 유연성 을 만족하면서 다양한 상황에 사용할 수 있다. 요구사항이 복잡하고 조금 더 다양한 상황을 고려해야 할 때 적절한 판단이 될 것이다. 합성 컴포넌트는 직접 조합해서 만들어야 하기 때문에 사용자에게 자율성을 주는 대신 편리성은 떨어진다. 이 두가지는 props를 사용하는 방식과 대비되어 트레이드오프가 될 것이라고 생각된다.

prop을 사용한 방식

  • 한눈에 이 Dialog에 checkBox가 어떻게 나열될지 알기 어렵다.
  • checkBox 중간에 안내 문구가 추가된다거나 했을 때 대응하기 어렵다.
<Dialog
    dimmed
    title="타이틀"
    checkBoxList={[
        {
            title: '버튼명',
            isChecked: true,
            hasArrowButton: true,
        },
          {
            title: '버튼명',
            isChecked: false,
            hasArrowButton: true,
        },
          {
            title: '버튼명',
            isChecked: false,
            hasArrowButton: true,
        },
    ]}
    labelButtonList={[
        { 
            title: '버튼레이블',
        }
    ]}
/>

합성 컴포넌트 방식

  • 훨씬 직관적이고 Dialog가 어떻게 생겼을지 추측하기 쉽다.
  • CheckBox 중간에 어떤 Dialog 요소가 추가된다고 하더라도, 서브 컴포넌트만 중간에 끼워 넣으면 요구사항을 바로 반영할 수 있음
// 합성 컴포넌트 방식. 훨씬 직관적이고 상황별로 유연하게 대처할 수 있습니다.
<Dialog>
  <Dialog.Dimmed />
  <Dialog.Title>타이틀</Dialog.Title>
  <Dialog.CheckBox isChecked hasArrowButton>
    버튼명
  </Dialog.CheckBox>
  <Dialog.CheckBox hasArrowButton>버튼명</Dialog.CheckBox>
  <Dialog.CheckBox hasArrowButton>버튼명</Dialog.CheckBox>
  {/* 혹시 여기에 무언가 설명이 들어가야 한다면 아래처럼 추가만 하면 됩니다. 더이상 이미 구현된 Dialog를 수정할 필요는 없습니다.
    <Dialog.Description>설명</Dialog.Description> 
  */}
  <Dialog.CheckBox hasArrowButton>버튼명</Dialog.CheckBox>
  <Dialog.CheckBox hasArrowButton>버튼명</Dialog.CheckBox>
  <Dialog.LabelButton>버튼레이블</Dialog.LabelButton>
</Dialog>

https://fe-developers.kakaoent.com/2022/220731-composition-component/

✅ 렌더링 최적화

렌더링 최적화라고 하면 거창해보이지만 리렌더링 과정을 한단계 줄인 경험을 이야기 하려고 한다. 테코톡 발표로 컴포넌트의 생명주기 를 준비하면서 state가 변경되는 시점, useEffect 의 실행시점에 대해 공부하고 있었다. 그래서 입력값에 따라 카드 브랜드가 결정되고 카드 브랜드의 최대 입력 길이가 결정되는데, 이벤트 핸들러가 실행되는 시점과 카드 브랜드의 최대 입력 길이가 결정되는 시점이 달랐다. 이를 이벤트 핸들러에서 판단하기 위해 카드 브랜드를 반환하는 함수를 만들었는데, 카드 브랜드 state와 반환값 2개로 관리되어 Single Source Of Truth 관점에서 적절하지 않다는 리뷰를 받았다.

이를 해결하기 위해 카드 번호가 리렌더링된 후 카드 브랜드값에 따라 카드 번호를 자르기 위해 useEffect를 사용할 수 있었다. 하지만 이럴 경우 렌더링 패스를 2번 모두 돌게 된다. 또한 사용자와 상호작용으로 발생한 로직이 useEffect에 위치한다는 점이 마음에 들지 않았고 이를 공식문서에서 적절하지 않다고 말한다.

그래서 새로운 값으로 리렌더링 하는 과정에서 if문을 통해 카드 브랜드 값에 따라 리렌더링을 발생시켜 자식을 렌더링 하거나 DOM을 업데이트하지 않아 렌더링 비용을 줄일 수 있었다.

// ❌ : useEffect 사용
const useCardNumbers = (...) => {
  ...
  useEffect(() => {
    ...
    카드브랜드_갱신(fullCardNumber);
    const 현재_유효_최대_길이 = 브랜드정보 ? 유효_최대_길이 : 16;

    if (카드번호_길이 > 현재_유효_최대_길이) {
      카드번호_갱신(카드번호.substring(0, 현재_유효_최대_길이), 'first');
    }
  }, [cardBrand]);
}

// ✅ : 렌더링 중에 렌더링 트리거
const useCardNumbers = (...) => {
  ...
	if (카드번호_길이 > 현재_유효_최대_길이) {
      카드번호_갱신(카드번호.substring(0, 현재_유효_최대_길이), 'first');
    }
  // useEffect(() => {}
}
profile
단단한 프론트엔드 개발자가 되고 싶은

0개의 댓글