레벨2 장바구니 - step1

개요

미션
기간
Repository
PR & Review
github pages
storybook

장바구니 1단계

23-05-09 - 23-05-14

미션 회고

레벨 2의 세 번째 미션은 장바구니 페이지를 만드는 것이다. 이번 미션을 바탕으로 백엔드와 협업 미션도 진행을 한다. 즉, 서버에 있는 데이터를 받아 상품 목록을 보여주고 장바구니에 상품을 담고, 삭제하는 과정도 구현해야 한다. 이를 위한 본격적인 기능 구현은 2단계에서 진행을 하게 된다. 1단계에서는 장바구니 마크업을 완성하고 상태를 관리하기 위한 빌드업 과정이라고 생각하며 미션에 임했다.

장바구니 1단계 미션의 필수 요구 사항은 다음과 같다.

  1. 상품 목록 페이지

    • 상품 목록 페이지에 필요한 UI 마크업

    • header의 숫자 표시를 통해 장바구니에 담긴 상품 종류의 갯수 표시

  2. 전역 상태 관리

    • recoil을 사용하여 전역 상태 관리

  3. mock 데이터 활용

    • Mock 데이터를 활용하여 상품 데이터를 처리한다. 협업 미션을 고려하여 장바구니 API 예상 명세 참고

  4. 테스트 도구 선정

    • 적합한 테스트 도구를 선택하여 사용하고, 중요한 테스트 케이스를 정의하여 테스트 진행

이전 미션에서는 contextAPI를 활용하여 전역 상태를 관리했따면 이번에는 recoil을 사용하여 전역 상태를 관리해야 했다. recoil이 중요한 것이 아니다. 무엇을 전역 상태로 관리해야 하는지를 고민하는 과정이 중요하다. 쉽게 결론이 날 수 있는 고민이지만 기준을 잡아가는 과정을 이번 미션에서 경험하고 싶었다.


👬 페어프로그래밍 진행 과정

장바구니 1단계 미션은 수아와 함께 진행하였다. 지금까지 eslint, tsconfig, path-alias와 같은 설정에 많은 어려움이 있었는데, 이번 미션에서는 물 흐르듯이 설정이 완료되어 빠르게 미션 요구사항을 충족하기 위한 코드 작성을 시작할 수 있었다. 때문에 미션 진행을 위한 이야기를 보다 많이 나눌 수 있었다.

나도 그렇도 수아도 그렇고 둘다 recoil를 사용해 본 적이 없었 뿐 더러 전역 상태 관리에 대한 경험이 많이 부족하여 무엇을 전역 상태로 관리를 해야 할지 고민을 함께 하였다. 함께 고민한 끝에 장바구니에 담긴 상품을 전역 상태로 만들었다. 그 이유는 장바구니에 담긴 상품의 상태는 서로 너무 먼 두 개의 컴포넌트에서 사용하기 때문이다. 뿐만 아니라, 2단계 미션에서 장바구니 페이지를 만들어야 할 때에도 해당 상태가 필요하기 때문이다. 또한 recoil의 selector의 기능을 사용하면 장바구니에 담긴 상품의 개수를 쉽게 알 수 있어 전역으로 상태를 관리하기로 하였다.

이번 미션도 스토리북을 활용하여 TDD를 적용하여 진행하고 싶었다. 이를 수아에게 말했고, 다행히도 수아도 이를 동의하여 가장 중요한 컴포넌트가 무엇인지 우선적으로 고민하고 이를 스토리북으로 만들면서 미션을 진행하였다. 아직 서툰 감이 있지만 조금씩 TDD, storybook에 적응이 되어 가는 것 같아 좋았고 수아도 TDD에 좋은 경험을 했다고 말해주어서 뿌듯하였다. 앞으로도 꾸준히 연습을 해봐야겠다.

1단계 미션에서는 비동기 통신이 없었지만, 2단계 그리고 앞으로 장바구니 협업 미션을 위해 Mock 데이터를 비동기로 받아와 로딩 화면을 만들었다. Suspense를 활용하여 로딩 화면을 만들고 싶었지만, Suspense 적용에 번번이 실패를 하여 아쉬운 마음이 들었다. 하지만 원하는 스켈레톤 UI는 잘 동작되어 다행이라고 생각했다.

페어프로그래밍을 진행하면서 수아와의 의견 차이가 거의 없었기 때문에 스무스하게 미션을 진행하였다. 물론 서로 처음 사용하는 기술이 많았기 때문에 버벅거림은 있었지만, 그때마다 공식문서, 기술 블로그를 보며 함께 해결하는 과정을 통해 원하는 방향으로 미션을 마무리하고자 노력했다. 아직 프롤로그 글쓰기에 대한 이야기를 하지 않았다. 다음 주에 이야기를 나누며 장바구니 미션에 대한 회고를 진행하려고 한다.😁


👨‍💻 PR Review

이번 미션의 리뷰어는 그토록 바라던 크론이었다! 다른 리뷰어도 좋았지만 우테코 코치로 근무 중인 크론에게 리뷰를 받고 싶었다. 다음은 이번 1단계 장바구니 미션에서 받은 코드 리뷰에 대한 내용이다.


Array.from()으로 배열 만들기

반복되는 컴포넌트를 랜더링을 할 경우, 보통 배열의 map 메서드를 사용한다. 배열이 있다면 map 메서드를 바로 사용할 수 있지만 배열을 임의로 만들어야 한다. 배열을 만드는 메서드도 사용해야 한다. 이번 미션에서는 다음과 같이 길이가 12인 배열을 만들어 12개의 ProductItem 컴포넌트를 랜더링 하였다.

new Array(12).fill(undefined).map((_, index) => <ProductItem key={index} />);

이와 같은 방법은 undefined으로 채워진 배열을 만들고 map으로 또 다른 배열을 만드는 방법이다. 즉, 배열을 2번 만들고 있다. 크론은 이 방법보다 Array.from() 방법에 대해 소개해주었고 리팩터링을 하여 적용한 코드는 다음과 같다.

Array.from({ length: 12 }, (_, index) => <ProductItem key={index} />);

Array.from() 은 유사 배열 객체, 반복 가능한 객체를 첫 번째 인자로 받고 이를 매핑 함수를 통해 새로운 배열로 만든다. 즉 위에서 유사 배열 객체인 { length: 12 }를 배열로 만들고 있는 것이다.


어딘가 어색한 컴포넌트 네이밍

장바구니 미션을 진행하면서 UserSummaryShoppingCart라는 컴포넌트를 만들었다. 해당 컴포넌트는 헤더에 존재하며 유저의 이름과 함께 유저의 장바구니 개수를 나타내는 컴포넌트이다.

이를 보다 재사용가능하게 만들고 보통의 서비스에서 사용하는 네이밍을 사용할 수 있도록 리팩터링을 하였다. 모바일에서는 아이콘과 숫자가 함께 있는 형태를 배지(Badge)라고 칭하는 것이 일반적이라고 한다. 때문에 장바구니의 배지이므로 CartBadge라는 이름으로 수정하였다.

추가적으로 앞으로 큰 서비스를 만들 때, 고려야 할 점은 위 사진에서 숫자가 포함되어 있는 동그란 모양을 독립적인 컴포넌트로 나누어 분리를 하는 것이다. 해당 컴포넌트는 말 그대로 Badge가 될 것이다. 하지만 이번 미션에서는 배지를 다루는 컴포넌트가 CartBadge 하나뿐이므로 분리를 하진 않았다.

만약, TDD를 할 때, 처음부터 더 작은 컴포넌트를 생각했다면 어땠을까? 하는 아쉬움이 남는다. 추가적으로 서비스에서 자주 사용되는 네이밍을 정리를 해두어 더 깔끔한 네이밍과 네이밍에 투자하는 비용을 줄일 수 있도록 해야겠다.


JSX에선 코드를 깔끔히

이 내용은 이번 미션에서 지속적으로 등장한 리뷰이다. 아마, 이전 미션들에서도 생각하지 않았던 부분이었을 것이다. 리액트에서는 JSX 문법을 사용하는데 이는 자바스크립트의 확장 문법이며 자바스크립트 파일을 HTML과 비슷하게 마크업을 작성할 수 있도록 도와준다. 때문에 리액트 컴포넌트의 JSX 문법은 화면에 보이는 UI/UX와 밀접한 관련이 있다고 생각한다. 이런 부분에 복잡한 로직이 들어있다면 코드를 읽는 입장에서는 무엇이 화면에 랜더링이 되는지 쉽게 파악할 수 없다. 지금까지 내가 그랬던 것처럼 말이다...

이번 미션에서의 코드를 예를 들어보면 다음과 같다.

<S.Quantity>
  {quantity > SHOPPING_QUANTITY.MAX ? `${SHOPPING_QUANTITY.MAX}` : quantity}
</S.Quantity>

여기에서 quantity는 장바구니에 담긴 상품 종류의 수량이다.(각 상품의 수량이 아니다.) 이를 JSX에서 삼항연산자를 사용하여 계산하고 있다. 보여지는 것과 계산하는 것. 무엇이 JSX의 역할인지 생각하면 당연히 보여지는 것이다. 때문에 계산하는 것은 JSX가 아니라 다른 곳에서 할 수 있도록 리팩터링을 진행하였다.

그렇다면 어디에서 장바구니에 담긴 상품 종류의 수량을 계산해야 하는지를 생각해야 한다. 현재 프로젝트에서는 장바구니 상태를 atom으로 관리하고 있기 때문에 selector을 사용하여 장바구니의 상태를 바탕으로 상품 종류의 수량을 계산할 수 있다. 결국 selector에서 반환하는 shoppingCartAmount를 그대로 JSX에서 사용하는 방법으로 리팩터링을 하였다.(quantity가 정확히 어떤 것의 수량인지 파악할 수 없어서 네이밍도 수정하였다.)

중요한 것은 가독성 측면을 생각한다면 간단한 로직이여도 JSX보단 다른 곳에서 선언하여 JSX에선 변수만 사용할 수 있도록 하는 것이다. 습관을 기르도록 하자.


JSX에선 삼항연산자 보단 early return

이번에도 JSX에 대한 내용이다. 특정 상태에 따라 완전히 다른 JSX가 return 될 경우가 있다. 이럴 때, 나는 보통 삼항연산자를 사용하였다. 간단히 한, 두줄인 경우엔 괜찮지만 삼항연산자가 너무 길어질 경우 코드를 읽기 힘든 상황이 찾아올 수 있다.

이번 미션에서는 다음과 같은 상황이 있다. 상품의 수량을 올리고 줄이는 컴포넌트인 QuantityController은 상품이 장바구니에 추가되었는지 그렇지 않았는지에 따라 다른 JSX가 return 된다.

  1. 상품이 장바구니에 추가되지 않은 경우 장바구니 아이콘이 return 된다.

  2. 상품이 장바구니에 추가되어 있는 경우 수량과 수량을 조절할 수 있는 버튼이 return 된다.

이를 코드로 간단히 나타내면 다음과 같다.

return quantity === SHOPPING_QUANTITYE.MIN ? (
  <div>장바구니 아이콘</div>
) : (
  <div>
    <div>장바구니 수량</div>
    <button>+</button>
    <button>-</button>
  </div>
);

실제 코드는 위의 내용보다 훨씬 길기 때문에 삼항연산자를 한 번에 파악하기엔 무리가 있었다. 때문에 이를 early return으로 리팩터링 하였다.

return (quantity === SHOPPING_QUANTITYE.MIN) {
	return <div>장바구니 아이콘</div>
}

return <div>
    <div>장바구니 수량</div>
    <button>+</button>
    <button>-</button>
</div>

이렇게 된다면 조금 더 코드를 읽기에 편하지 않을까 싶다.

그렇다면 '전체가 바뀌는 것이 아니라 부분이 상태에 따른 바뀌는 부분은 어떻게 해야 할까?'라는 의문이 들었다. 심지어 그 부분도 길다...라고 하면 어떻게 해야 할까? 아마 그런 상황이 존재한다면 컴포넌트 분리를 다시 한번 생각을 해야 하지 않을까 싶다.


이벤트를 다루는 함수의 네이밍

지금까지 이벤트를 다루는 함수를 만들 때 handle이라는 접두사를 사용하였다. 예를 들어 handleClickCartIcon 함수는 "CartIcon를 클릭했을 다루는 함수구나!"라는 것을 알 수 있기 때문이다. 하지만 어떤 동작을 하는 것인지는 파악할 수 없다. 클릭을 해서 무엇을 한 것인지는 함수의 내부를 살펴봐야 한다. 즉, 코드를 읽고 파악하는데 비용이 든다.

그렇다면 어떻게 네이밍을 해야 할까? 처음 고민했던 부분은 다음과 같다.

  • handle~ + 동작을 나타내는 네이밍

위와 같은 조합은 해석이 어색하다고 느껴진다. 때문에 동작을 나타내는 네이밍에 초점을 두었다. 예를 들어 위의 handleClickCartIcon은 클릭을 하면 장바구니에 상품이 추가되므로 addCartItem으로 리팩터링 하였다. 그래도 조금 더 고민하면 handle 접두사도 사용할 수 있지 않을까 싶다

앞으로 다른 개발자와 협업을 하는 기회가 많이 있을 것이다. 그때마다 함수의 내부 동작을 일일이 읽는 것보다 함수의 이름을 통해 예상 동작을 빠르게 파악한다면 코드 읽는 비용이 줄어들기 때문에 협업 효율이 늘어날 것이다. 지금까지 가지고 있었던 습관을 고쳐보자.

추가로, 이벤트 핸들러에 대한 좋은 자료가 있어 기억하고자 남긴다. https://blog.shiren.dev/2020-07-27-1/


Barrel Pattern

이번 리뷰 내용에서 가장 난해한 부분이었다고 생각한다. 자료가 많이 있지 않을뿐더러, 나의 코드에서 어떤 부분이 Barrel Pattern이었는지 잘 몰랐기 때문이다.

일단 다음은 Barrel Pattern를 소개하고 있는 외국 블로그이다.

https://medium.com/swlh/import-components-in-react-like-a-pro-b1340cb76a1b

이를 간단히 요약하자면 Barrel Pattern이란 여러 컴포넌트를 각각 다른 파일에 import를 사용하여 가져오는 것이 아니라 하나의 파일(예를 들어 index.ts)에서 모든 컴포넌트를 import를 하고, 컴포넌트가 필요한 곳에선 index.ts 하나만 import 하여 필요한 컴포넌트를 불러와 사용하는 패턴이다.

당연히 이와 같은 패턴은 문제가 발생한다. 사용하지 않는 컴포넌트도 함께 불러오기 때문이다. 그렇게 된다면 결과적으로 bundle.js로 포함되는 배포 파일의 용량이 커지고 이는 화면 로딩이 오래 걸리는 성능 문제로 이어진다. 이를 Tree Shaking 이슈라고 한다.

하지만 나의 코드에선 이와 같은 패턴을 찾을 수 없다고 생각한다. 비슷한 것이 있다고 하면 스타일 컴포넌트인데 Barrel Pattern과 다른다고 생각하는 것은 모든 스타일 컴포넌트를 낭비 없이 사용하고 있다는 것이다. 즉, 필요 없는 스타일 컴포넌트를 가져오지 않고 있다.

Barrel Pattern. 생소한 패턴이고 다른 크루들에게 물어봐도 처음 들어봤다는 크루들이 대부분이었다. 앞으로 다양한 코드와 구조를 보게 될 텐데, 보게 된다면 Tree Shaking 이슈를 한 번 언급해 볼 만하다. 물론 난 사용하지 않을 것이다.


👍 잘한 점

  1. 리뷰 받은 내용을 꼼꼼히 살펴보고 적용할 수 있는 부분은 최대한 적용하였다.

  2. 배포 과정에서 어려움이 있었지만 제한 시간 내에 잘 해결하였다.

👎 아쉬운 점

  1. 회고를 너무 늦게 하였다.

  2. 아직 코드 레벨에서 고쳐야 할 잘못된 습관이 보인다.

👊 앞으로의 각오

  1. 네이밍은 계속 가져가야 할 고민거리이지만 코드 레벨에선 최선의 방법을 사용하도록 하자.

  2. 글쓰는 것을 미루지 말자..


📅 2023-05-27

Last updated