새소식

우테코

[우테코 FE 6기] 레벨2 미션3 장바구니 미션 회고 🛒

  • -
반응형

장바구니 레츠 고 ~

 

 

레벨 2 3번째 미션은 장바구니 미션이었고 정말 재밌던 미션이었다.

 


 

먼저 장바구니 미션의 📍 학습 목표

 

✔️ Recoil을 사용하여 클라이언트 상태를 관리할 수 있다.

✔️ React Testing Library(RTL)를 활용하여 주요 기능에 대한 테스트를 작성할 수 있다.

 

리코일을 사용하여 상태 관리를 하고, RTL을 활용하여 테스트를 진행하는 미션이었다.

 


 

🍀 Recoil 잘 사용하기

 

recoil은 이전에도 전역 상태 관리를 위해 사용해 본 적이 있었다. 하지만 atomFamily 나 selector와 같은 기능에 대해서 처음 사용해 보게 되었다.

 

atomFamily 란?

atomFamily 를 사용하면 여러 개의 상태를 동적으로 생성할 수 있어, 같은 구조를 가지면서 서로 다른 인자를 가지는 여러 상태를 쉽게 만들 수 있었다. 아래 코드처럼 장바구니의 각 아이템을 별도의 상태로 관리할 수 있었다.

 

export const filteredCartItemState = atomFamily<
  FilteredCartItemStateType,
  number
>({
  key: 'cartItemState',
  default: INIT_CART_ITEM_STATE,
  effects_UNSTABLE: (id) => [localStorageEffect(`cartItemState_${id}`)],
});

 

selector란?

selector는 하나 이상의 atom이나 다른 selector의 상태를 읽고, 이를 기반으로 파생된 상태를 생성하는 데 사용할 수 있었다.

  • 파생된 상태 생성: 여러 개의 atom의 상태를 결합하거나 변형하여 새로운 상태를 생성할 수 있다.
  • 자동 업데이트: 참조하는 atom이나 다른 selector의 상태가 변경될 때 자동으로 업데이트된다.
  • 동기 및 비동기 계산 지원: 동기적으로 계산된 값뿐만 아니라 비동기적으로 계산된 값도 지원한다.

 

그러다보니 장바구니에서 선택된 상품의 금액을 계산할 때 매우 유용했다.

 

이 코드를 보면 atom으로 저장된 선택 주문한 아이템과 배송비 등의 데이터를 사용하여 상품의 총 가격, 배송비, 그리고 총 가격 등을 selector 내부에서 구하여 객체 형태로 넘겨받을 수 있다.

 

또한 이 결과는 orderItemState 가 변하거나, shippingFeeState의 상태가 변하면 이를 구독하여 새로 변경된 데이터로 업데이트 되어서 바로바로 변경사항을 확인할 수 있었다.

 

export const orderRecipeState = selector<Recipe>({
  key: 'orderRecipeState',
  get: ({ get }) => {
    const orderedList = get(orderItemState);
    const shippingAreaFee = get(shippingFeeState);

    const orderPrice = orderedList.reduce((acc, cur) => {
      acc += cur.product.price * cur.quantity;

      return acc;
    }, 0);

    const shippingFee =
      orderPrice >= FREE_SHIPPING_CONDITION ? 0 : shippingAreaFee;
    const totalPrice = orderPrice + shippingFee;

    return {
      orderPrice,
      shippingFee,
      totalPrice,
    };
  },
});

 

 

또한 비동기 통신을 호출할 때도 유용하게 사용되어 useEffect 를 사용하지 않아도 데이터를 한 번만 호출할 수 있었으며, strickMode로 두 번 호출되는 문제도 발생하지 않았다.

 

사용법은 selector 내부에서 데이터를 호출하고 이를 atom의 default 로 넣어주는 것이다.

export const cartState = selector({
  key: 'cartState',
  get: async () => {
    const cart = await getCartList();
    return cart;
  },
});

// atom 
export const cartListState = atom<CartItemType[]>({
  key: 'cartListState',
  default: cartState,
});

 

 


💅 emotion style 선택 사용

 

매번 스타일드 컴포넌트를 사용했던 거와 달리 이번에는 이모션 스타일을 사용해보았다.

 

이모션이 기본 패키지인 @emotion/react와 @emotion/styled는 최소한의 런타임 크기를 가지고 있어 번들 크기를 줄이는 데 도움이 된다고 하고 이전에 사용해보지 않아서 이번 기회에 공부해 보기 위해 선택했다.

 

((그리고 토스 팀에서 주로 사용한다길래 도전해 보았다))

 

이모션은 Styled API를 지원하여 Styled Components와 유사한 styled API를 제공하여 익숙한 방식으로 사용할 수 있어서 사용하는데 큰 불편함이 없었다.

 

이를 공부하다가 CSS 프로퍼티를 알게 되어, 매우 유용하게 사용하였는데, 이렇게 css 함수를 사용하여 자주 사용되는 스타일을 간단하게 스타일을 정의할 수 있었다.

 

export const WhiteSpace = css`
  padding-right: 16px;
  padding-left: 16px;
  box-sizing: border-box;
`;

export const FlexSpaceBetween = css`
  display: flex;
  justify-content: space-between;
`;

export const FlexCenter = css`
  display: flex;
  justify-content: center;
  align-items: center;
`;

// 사용시 
const Box = styled.div`
	${FlexCenter}
	...
`;

 

 


 

🌴 낙관적 업데이트

 

이번 미션을 진행하며 낙관적 업데이트에 대해서 많은 의견을 나누었다.

 

낙관적 업데이트란?

클라이언트에서 상태를 즉시 업데이트하여 사용자가 변경 사항을 빠르게 확인할 수 있도록 하는 방법이다. 서버에서 실제로 데이터가 성공적으로 업데이트되었다고 확인되기 전에, 먼저 클라이언트에서 상태를 업데이트하는 방식으로, 사용자가 애플리케이션을 보다 빠르고 즉각적으로 반응하는 것처럼 느끼게 한다.

 

하지만 낙관적 업데이트에는 위험이 있는데, 만약 서버에서의 업데이트가 실패한다면, 클라이언트에서 이미 변경된 상태를 다시 롤백해야 하며, 그전까지 잘못된 데이터를 보여준다는 것이다.

 

 

그래서 이를 허용하지 않기 위해 어떤 방법이 있을까 고민했다.

  1. 수량 변경 후 다시 get을 통해 데이터를 호출하여 수량 데이터를 확실시한다.
  2. 수량 변경 api 호출 뒤 다음 함수가 실행된다면 응답이 200 OK라고 판단하여 수량을 업데이트한다.

아무래도 가장 확실한 방법은 전자였지만 이렇게 되면 수량이 하나 변경될 때마다 두 번의 호출이 계속해서 반복된다는 것이 마음에 들지 않았다. 또한 데이터가 너무 늦게 올 경우 UX 가 매우 떨어질 수 있었다.

 

그래서 초반에는 전자로 작업하다가 이후 페어와의 상의를 통해 후자의 방식을 선택하였다.

 

그리고 리뷰어에게도 이에 대해 문의해 보았다.

 

 

 

다행히 후자의 방법도 낙관적 업데이트가 아니라고 생각하셔서 계속해서 후자의 방식으로 진행해 보았다.

 


 

🥊 TDD로 작업하며 커스텀 훅 제작하기

 

step2에서는 다양한 쿠폰을 적용할 수 있게 기능이 추가되었는데 그러다 보니 복잡한 파생 상태를 관리했어야 했다.

다양한 로직을 깔끔하게 정리하기 위해 TDD 적으로 로직 구현을 진행해 보았다. 자세한 방식은 이미 작성한 글이 있어서 따로 남겨놓겠다.

 

 

그래서TDD를 통해 순서대로 로직을 구상해 보았다.

 

  1. 쿠폰의 유효성 검사
    1. 쿠폰이 존재하는지, 쿠폰의 유효기간 등이 유효한지 쿠폰 자체에 대한 유효성 검사하는 훅
  2. 쿠폰의 사용 가능 유무 검사
    1. 사용자가 이 쿠폰을 사용할 수 있는지 확인하는 훅 (최소 금액, 구매 시간, 특정 사용 가능 조건 판별)
    • e.g.) 3만 원 이상 구매인지, 4시 특가 딜일 때 시간 확인, 2+1 행사 시 3개 이상 선택했는지
  3. 해당 쿠폰을 사용할 때 받는 할인 금액
    1. 하나의 쿠폰을 사용할 때 받을 수 있는 할인 금액을 리턴하는 훅
  4. 모든 쿠폰 선택 후 받을 수 있는 최종 할인 금액
    1. 두 개 이하의 쿠폰을 선택하여 사용할 때 받을 수 있는 최대 할인 금액을 리턴하는 훅

 


 

🧑‍🎤 미션을 진행하며 신경 썼던 부분

 

 

1. 페이지 되돌아갈 시 이전 데이터 초기화

초반에는 생각하지 못했던 부분이지만 기능 구현을 거의 다 마치고 발견했던 버그였다.

처음에 하나의 물건을 5개 주문해 10만 원 이상의 금액을 주문하여 쿠폰을 5만 원 이상 할인 쿠폰, 2+1 구매 할인 쿠폰을 선택했다가 다시 뒤돌아가 물건을 취소하여 1만 원짜리 물건 하나를 선택하여 다시 재주문하게 되면 리코일로 이미 저장한 쿠폰 데이터 때문에 선택될 수 없는 쿠폰들을 사용하게 되는 버그가 있었다.

 

이를 위해 주문 페이지가 마운트 될 때 선택된 쿠폰 리스트 상태를 초기화해 주는 기능을 추가했다.

 

2. 배송 지역 선택 상태 초기화

앞에 이야기와 비슷하게 주문 페이지에서 이 배송이 도서 산간 지역 인지 여부를 선택하여 선택 시 배송비가 6000원이 되었는데 이후 다시 장바구니로 돌아가게 되면 이 업데이트된 배송비 상태가 보이는 버그가 발생하였다.

 

이를 해결하기 위해 장바구니 페이지가 마운트 될 때 배송 지역 상태를 초기화해주는 기능을 추가했다.

 

 


 

 

이외 모든 코드는 아래 링크에서 자세히 확인할 수 있다.

 

 

Github

 

GitHub - healim01/react-shopping-products

Contribute to healim01/react-shopping-products development by creating an account on GitHub.

github.com

 

 

react-shopping-products

 

healim01.github.io

 

반응형
Contents
-

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.