장바구니에 옵션을 추가할 때, 중복성 처리하기

이나리·2022년 7월 22일
0

문제의 코드

다음 코드는 장바구니 리스트(cart)와 사용자가 장바구니에 추가할 옵션 리스트(options) 입니다.

interface Option {
  name: string;
  size: string | number;
  quantity: number;
}

const cart: Option[] = [
  { name: 'uniform', size: 80, quantity: 1 },
  { name: 'uniform', size: 85, quantity: 2 },
  { name: 'clothing', size: 'S', quantity: 1 },
];

const options: Option[] = [
  { name: 'uniform', size: 80, quantity: 1 },
  { name: 'uniform', size: 90, quantity: 1 },
  { name: 'cap', size: 'M', quantity: 2 },
];

사용자가 개별 상품 페이지에서 원하는 구매 옵션을 선택하고, 이를 장바구니에 추가할 때,
장바구니 리스트와 사용자가 선택한 옵션 리스트 사이에 중복된 상품이 존재할 가능성이 있습니다.
이때, 중복된 옵션과 중복되지 않은 옵션을 구별하여 새로운 장바구니 리스트를 생성해야 합니다.

  • 중복된 옵션이라면? 중복된 옵션을 찾아, 수량만 변경합니다.
  • 중복되지 않은 옵션이라면? 옵션을 장바구니 리스트에 추가합니다.

문제 해결 과정

장바구니 리스트와 사용자가 선택한 옵션 리스트를 각각 비교하기

보통 중복된 값을 걸러내기 위한 방법으로는 filter 메서드를 이용해 true 인 경우만 찾아내는 방법이 있는데, 제 경우에는 중복이 있다면 특정 프로퍼티의 값을 변경시켜야 해서, 조금 까다롭게 느꼈습니다.

reduce 메서드는 각각의 배열 요소에 전달된 콜백 함수를 실행해 최종적으로 계산된 마지막 값을 리턴합니다. 그래서 이를 이용해 요소를 하나씩 검사하면서 중복성을 검사해봤습니다.

const newCart = cart.reduce<Option[]>((prev, curr) => {
  const sameOption = options.find(
    option => option.name === curr.name && option.size === curr.size
  );
  return sameOption
    ? { ...prev, quantity: curr.quantity + sameOption.quantity }
    : prev;
}, []);

//  const newCart = [
//      { name: 'uniform', size: 80, quantity: 2 },
//      { name: 'uniform', size: 85, quantity: 2 },
//      { name: 'clothing', size: 'S', quantity: 1 },
//  ]

그런데 문제는, 한쪽 배열을 기준으로 배열 메서드를 실행하기 때문에, 기준이 되는 배열에서 중복되는 옵션이 있을 경우에 수량을 변경시킬 수는 있지만, 중복되지 않는 옵션을 기존값에 추가하기가 어려웠습니다.

장바구니 리스트와 사용자가 선택한 옵션 리스트를 모두 합친 후, 비교하기

이번에는 두 값을 먼저 합친 후, 새로운 배열에서 리스트를 순회합니다.
이렇게 하면, 기존에 존재하는 장바구니 리스트와 새로 추가하려는 옵션 리스트를 모두 순회할 수 있기 때문에 중복 요소를 쉽게 변경할 수 있을 것 같습니다.

const newCart = cart.concat(options).reduce<Option[]>((prev, curr) => {
  const sameOptionIndex = prev.findIndex(
    prev => prev.name === curr.name && prev.size === curr.size
  );

  if (sameOptionIndex !== -1) {
    prev[sameOptionIndex].quantity += curr.quantity
    return prev;
  }

  prev.push(curr);
  return prev;
}, []);

일치하는 옵션이 없다면, 해당 옵션을 이전 값에 추가합니다.
일치하는 옵션이 있다면, 일치하는 옵션을 찾아서 수량만 변경하고 해당 옵션은 추가되지 않도록 기존 값을 리턴하도록 하면 중복된 옵션은 추가되지 않고 수량만 증가시킬 수 있습니다.


마지막으로, 처음에 생각했던 filter 메서드를 이용해, 중복성을 처리하는 방법에 대해서도 생각을 해봤는데요.

여기서는 reduce 메서드와 달리, 배열 메서드에서 메서드를 실행한 배열, 즉 자기 자신을 참조할 수 있는 방법을 적용해봤습니다.

const newCart = cart.concat(options).filter((item, index, array) => {
  const sameOptionIndex = array.findIndex(
    arr => arr.name === item.name && arr.size === item.size
  );
  
  if (sameOptionIndex === index) return true;
  array[sameOptionIndex].quantity += item.quantity;
});

이전처럼 findIndex 메서드를 사용해서, 현재 실행 중인 배열 요소 값 item 의 인덱스와 자기 자신을 참조한 array 의 인덱스를 비교합니다.
이 값이 일치하지 않으면, 중복되는 옵션이 존재하는 경우에 해당됩니다.

이 경우에는, 인덱스로 찾은 해당 값의 수량을 변경해주고 아무 것도 리턴하지 않도록 하면 filter 메서드가 true 인 값만 걸러주기 때문에 중복 옵션을 필터링할 수 있습니다.


두 방법은 비슷하지만, 약간의 차이가 있습니다.

findIndex 메서드를 이용해 찾은 중복 인덱스의 값을 처리하는 방식이 약간 다릅니다.
인덱스를 검색하는 배열의 초기값이 다르기 때문입니다.

reduce 메서드의 경우에는 빈 배열이라는 초기값을 전달하고, 중복 옵션이 아닌 경우에만 해당 옵션을 하나씩 추가하는 방식이기 때문에, 동일한 옵션이 없다면 리턴하는 -1 값을 사용했습니다.

하지만, filter 메서드의 경우에는 자기 자신을 참조하여 배열을 순회합니다.
즉, 무조건 요소가 존재하기 때문에 findIndex 메서드로 찾은 인덱스 값은 항상 0 이상 이 되므로, -1 값을 사용할 수 없습니다.

0개의 댓글