TIL- SessionStorage로 장바구니를 만들어보자

sanghun Lee·2020년 8월 30일
1

Today I Learned

목록 보기
55/66

삽질의 개요

태어나서 웹개발 분야의 첫 협업프로젝트이다보니 서비스 단 마다의 협업 구성이 어떻게 되는지 몰랐습니다.

그래서 .. Front-end단에서 수량증감, 삭제, 가격계산, 선택제품 장바구니에 유입 등을 다 해결해야하는줄 알았습니다.

그러다 보니 자연스럽게 서로 상이한 페이지에 동일 정보를 넘겨 활용해야겠다는 생각이 들었고 그 과정을 통해 sessionStorage를 응용하게 된 어찌보면 삽질이고 어찌보면 큰 수획인 몇가지 코드들을 기록합니다.

삽질 중

일단 이야기하기 앞서 제가 구성한 상품 리스트 페이지는 여러가지 상품이 존재하고 각 제품마다 장바구니 버튼이 존재합니다.(캡쳐실력이 나빠도 계속 봐주세요)

따라서 product라는 각 제품을 나타내는 컴포넌트를 만들어 map method를 이용하여 이를 활용하였습니다

그 과정에서 장바구니 버튼에 getItem이라는 메서드를 선언해주었습니다.

    getItem = (id, price, name, mainImgSrc) => {
	let getData = {
     id: id,
       price: price,
      name: name,
    mainImgSrc: mainImgSrc,
     };
     sessionStorage.setItem(`${id}`, JSON.stringify(getData));
     this.props.history.push("/shoppingbag");

render된 해당 제품의 id를 이용하여 id를 key값으로 getData라는 정보를 넣어줬습니다.

아래는 sessionstorage에 저장된 값입니다.

SessionStorage에 저장된 값을 이용하여 해당 제품의 정보를 장바구니 페이지에 렌더시키는 자료로 이용하였습니다.

아래 코드를 통해 componentDidMount()라는 life-cycle method를 이용하여 render되는 순간 pickItem이라는 메서드에 저장을 해줬으며 count값이 따로 지정되지 않을 것이라는 예상으로 map method를 이용해 따로 값을 만들어 넣어줬습니다.

또한 각 아이템들의 개별 선택이 필요하다 판단해 boolean 값을 특정 메서드마다 선언해서 넣어줬습니다.

      checkItem = (item) => {
    const newPickItem = this.state.pickItem.map((el) =>
      el.name === item.name ? { ...el, active: true } : el);
    this.setState({
      pickItem: newPickItem,
    });
  };

그리고 각 수량의 단순계산을 위해 plus와 minus함수들을 만들었으며
counting과 동시에 총 금액이 render되어야 하는 문제로 인해 거의 모든함수에 callback이 걸려있는것을 확인할 수 있습니다.

➕ function

  countPlus = (item, inDecrement, e) => {
    const plusValue = this.state.pickItem.map((el) =>
      el.name === item.name
        ? {
            ...el,
            price: (el.price * (el.count + 1)) / el.quantity,
            quantity: el.quantity + inDecrement,
          }
        : el
    );
    if (inDecrement === +1) {
      if (item.quantity === 5) {
        alert("최대주문수량 5개");
        return;
      }
      this.setState(
        {
          pickItem: plusValue,
        },
        () => this.calculatePrice()
      );
    }
  };

➖ function

  countMinus = (item, inDecrement) => {
    const minusValue = this.state.pickItem.map((el) =>
      el.name === item.name
        ? {
            ...el,
            price: Math.floor((el.price * (el.quantity - 1)) / el.quantity),
            quantity: el.quantity + inDecrement,
          }
        : el
    );
    if (inDecrement === -1) {
      if (item.quantity === 1) {
        return;
      }
      this.setState(
        {
          pickItem: minusValue,
        },
        () => this.calculatePrice()
      );
    }
  };

Total Price function

총액의 경우 Reduce함수의 기능을 이용하여 간단하게 구현하고자 노력했으니 결국 하드코딩이 된 느낌입니다.
totlaPrice라는 state에 저장된 값을 곧바로 렌더하는 부분에 위치 시켜줬습니다.

  calculatePrice = () => {
    const sumPrice = this.state.pickItem
      .map((el) => el.price)
      .reduce((a, b) => a + b, 0);
    this.setState(
      {
        totalPrice: sumPrice,
      },
      () => this.shippingfeeDelete()
    );
  };

Shipping fee function

배송비의 경우 3항연산자를 이용해 간단하게 만들어 다른 함수안에 집어 넣을까 생각도 했으나 divide and conqure가 진리라는 어떤분의 말을 기억하며 나눠놨습니다.

또한, 엄밀히 생각하면 총액계산의 분야도 갯수 counting의 부분도 아니기 때문에 따로 나누는 것이 맞다고 생각이 듭니다.

  shippingfeeDelete = () => {
    const { totalPrice } = this.state;
    this.setState({ shippingFee: totalPrice >= 50000 ? 0 : 2500 });
  };

deleteSpecificItem function(X button)

무슨이유인지는 모르겠으나 클론한 사이트의 특징은 각 제품마다 삭제버튼이 존재함에도 불구하고 체크한 제품을 삭제하는 기능이 하나 더 탑재되어 있습니다.

해당 코드는 각 제품마다 가지고 있는 버튼을 이용하기 위한 메서드입니다.

선택된 특정아이템을 삭제하는것은 filter 메서드를 이용하여 아이디를 구별한 뒤 state값에 저장하여 제거하는 방식이며 세션스토리지에서 해당 아이디를 제거하는 방식으로 진행하였습니다.

  deleteList = (item) => {
    this.setState(
      {
        pickItem: this.state.pickItem.filter((el) => el.name !== item.name),
      },
      () => this.calculatePrice()
    );
    sessionStorage.removeItem(item.id);
  };

deleteWholeItem function

기존에 sessionStorage를 생각하지 않고 오로지 mockdata만을 작업을 할 당시에는 state를 그냥 ''로 setting해버리고 끝냈습니다만..
sessionstorage를 생각한 뒤로 함수가 조금 아니 많이 변경되었습니다.

  clearList = () => {
    this.setState({ pickItem: [] }, () => this.calculatePrice());
  };

deleteCheckedItem function

checkItem 이라는 간단한 함수를 통해 체크가 되면 active false값을 그렇지 않다면 true 값을 준 뒤 해당하는 id를 뽑아내 그 id 값만 지워 내주는 함수입니다.
버튼과 달리 클릭시 다른 컴포넌트에서 버튼이 존재하여 값을넘겨주는것에 힘듦이 컸습니다

다시 살펴보니 가능한것은 하나의 아이템만 체크했을 때 입니다
만약 배열로 값이 나온다면 해당 배열을 통해 forEach등의 메서드를 통해 모든 값이 적용되도록 함수 변경이 필요합니다

  deleteCheckedItem = (e) => {
    const forDeletingId = this.state.pickItem.filter(
      (el) => el.active === true
    );
    const getIdforDeleting = forDeletingId.map((el) => el.id);
    this.setState(
      {
        pickItem: this.state.pickItem.filter(
          (el) => el.id === getIdforDeleting
        ),
        emptyDisplay: this.state.pickItem.length === 0 ? true : false,
      },
      () => this.calculatePrice()
    );
    sessionStorage.removeItem(getIdforDeleting);
  };

SetFirstPrice function

초기값또한 계산을 하여 매번 업데이트 되도록 해야했습니다

처음부터 callback을 걸어두어 매번 실행되도록 만들었는데 이는 이벤트가 발생하지 않으면 함수가 발동하지 않아 총액의 값이 비어있었기 때문입니다.

정말 보기도 불편한 함수입니다

  setFirstPrice = () => {
    this.setState(
      {
        totalPrice: this.state.pickItem
          .map((el) => el.price)
          .reduce((formerPrice, latterPrice) => formerPrice + latterPrice, 0),
      },
      () => this.calculatePrice()
    );
  };

느낀점

이외에도 선택된 아이템만 Checkout창으로 넘어가는 등의 함수를 만들어 sessionstorage를 활용해보려 노력했습니다.

이 과정을 통해서 느낀점은 convention이란것이 괜히 존재하는것이 아니구나.. 라는 것입니다.

보통의 장바구니만들기는 프론트에서는 render의 개념이 전부라 위와같은 계산 함수들은 대부분 백엔드에서 이루어진 뒤 다시 조정된 값을 가진 정보들을 프론트로 보내줍니다.

위와 같은 코드들이 모두 프론트에 있다면 코드의 불편함은 물론이고 보안이라고 볼 수 없는 sessionstorage에 제품의 id 나 가격과 같은 상세정보들이 고스란히 나타난다는 점이 그닥 좋지는 않습니다.

또한 react를 통해 작성해서인지 render과정 Lifecycle의 이해등이 매우 굉장히 필요한 것같아 머리도 아픕니다

위에 기재된 코드들은 아직 완벽하게 작동한다고 자랑스럽게 말을 할 수는 없을 것 같습니다.

결론은 무지를 통해 경험한 기회며 학습이었어서 당황스럽지만 그래도 꾸역꾸역 만들어낸 제 자신한테 나름 좋기도 합니다.

문제는 코드가 상당히 더럽고 보기조차 불편하지만 추후에 시간이 되는데로 로직을 조금씩 수정해서 간편한 코드를 한번 만들어 수정본을 올려봐야겠습니다.

그럼 뿅

profile
알고리즘 풀이를 담은 블로그입니다.

0개의 댓글