프로젝트-1 1주차 [React 장바구니 기능]

최정환·2022년 4월 2일
0

wecode 프로젝트-1

목록 보기
1/2

사이트를 선정해서 클론코딩하는 프로젝트 1주차가 시작되었다.

ZARA HOME을 주제로 결정했다.

내가 맡은 page layout

장바구니 페이지를 맡았다.

컴포넌트를 나누는것을 우선으로 두고 일단 먼저 코드를 작성해서 대충 맞춰보자고 생각해 내가 생각한대로 컴포넌트를 쪼개서 만들었다.

파란색이 컴포넌트고 빨간색은 같은 상태(count)를 공유하고 있다.

☕️ 컴포넌트를 이렇게 나눈 이유
1. 장바구니와 나중에 쇼핑하기에 따라 나타내는 데이터가 다르다.
2. 장바구니안에 주문하기 박스는 제품이 존재하지 않을 경우 제품과 함께 사라지고 '장바구니에 물건이 없습니다'라는 글자를 띄어야한다.
3. 제품은 한개만 있는게 아니라 장바구니에 존재하는 데이터들을 다 표현해야한다. (제품카드를 따로 만든다.)

어려웠던 점

  1. 주문창은 position:fixed이며 밑에 footer를 넘어가는 순간엔 footer밑으로 들어가야한다.
    장바구니 컴포넌트에서 나가게 되면 주문창이 footer밑에 들어가야하는데 viewport가 컴포넌트 기준이 아니라 밑에 계속 따라온다.
    아직 해결하지 못했다.

  2. 장바구니, 나중에 쇼핑하기 nav bar 밑에 있는 검은색 줄 처리하는 방법
    className을 따로 만들어서 react event를 통해 동적으로 바꿔준다.




기능 구현

  1. 장바구니, 나중에 구매하기 클릭시 다른 컴포넌트 그리기
const [card, setCard] = useState(<BasketCard />);
const [line, setLine] = useState('basket');
                      
const change_basket = event => {
    const { className } = event.target;
    if (className === 'basket' || className === 'basket_out') {
      setLine('basket');
      return setCard(<BasketCard />);
    } else {
      setLine('shop-later');
      return setCard(<ShopLaterCard />);
    }
  };
.basket {
     @include nav_box(black);
    }
    .basket_out {
      @include nav_box(silver);
    }
    .shop_later {
      @include nav_box(black);
    }
    .shop_later_out {
      @include nav_box(silver);
    }

처음에 글자 밑에 있는 검은색 줄이 컴포넌트에 따라 다르게 그려져야하기 때문에 어떤식으로 접근을 해야할지 막막했었다.

이벤트에 따라 react에서 css가 바뀌는 방법을 검색해서 찾은 방법은 className을 먼저 css로 만들어 놓고 동적으로 className을 바꾸는 것이었다.

처음 페이지에 도착했을때 장바구니 컴포넌트를 보여주고 장바구니, 나중에 쇼핑하기를 누르면 그 className을 받아서 현위치에 있는 컴포넌트를 바꾸고 밑에 라인도 다르게 설정해줬다.


2. 장바구니가 비었다면 '장바구니가 비었습니다'
  useEffect(() => {
    items = items.filter(i => i.later === 0);
    items.length > 0 ? setIsNone(false) : setIsNone(true);
  }, [items]);

items는 받은 데이터다. items는 useContext를 통해 전체를 받아오는데 items 중에서 later(나중에 살건지 정하는 key를 추가함)가 0인 데이터만 받아서 사용하는데 만약 이 items가 없다면 '장바구니가 비었습니다'라는 글을 띄운다.

실시간으로 렌더링을 하기 위해

isNone || totalCounts() === 0
// totalCounts는 지금 몇개의 later가 0인 제품들이 있는지 계산하는 함수

를 조건으로 두었다.

함수의 결과를 조건문으로 두는건 이뻐보이지 않아서 리팩토링할때 어떻게 할지 생각해봐야겠다.


  1. 상태 공유하며 바뀔때마다 렌더링

    상태를 공유하는 컴포넌트가 많아서 처음 프로토타입을 만들때 자식에게 넘겨주는 함수, 상태가 너무 많아서 너무 코드가 더러워지고 갈피를 잡지 못했는데 멘토님이 context API를 알려주셨다.

    상태공유라는게 라이브러리로만 사용하는 것만봐서 이런 기능은 라이브러리로만 있는 줄 알았지만 새롭게 배우게 되었다.

    context api를 사용해 자식에게 상태를 주는 것은 순조로웠지만 장바구니 페이지 자체를 re-rendering을 해야 상태가 update가 되었다.
    아래와 같은 상태가 되어서 어떤식으로 해결을 해야할지 막막했다.

    위처럼 장바구니를 한번 눌러 re-rendering되지 않는다면 상태가 바로바로 update가 되지 않았다.

    처음 생각했던건 어쨋든 items의 구성요소가 바뀌는 것이니 최상단의 부모 useEffect deps에 items를 넣는다면 해결되지 않을까 였다. 하지만 useEffect안에는 items를 설정해주는 setItems가 있어 deps에 items를 넣는다면 무한 re-rendering이 되면서 오류를 내었다.

    두번째는 최상단 부모에서 useState를 하나 의미 없는 것을 만들어 제품카드에 있는 버튼까지 내려보내 버튼을 누르면 의미없는 상태를 갱신시키며 강제로 re-rendering을 시키는 것이었다.
    하지만 어차피 공부하는 입장에서 너무 의미없고 해봤자 근본적인 해결책이 되지 않을것같아 하지 않았다.

    세번째는 최상단 부모에서 deps에 spread연산자를 사용해서 무한 re-rendering을 없애는 것이었다.
    하지만 결국엔 re-rendering의 trigger가 없어서 redering 자체가 되지 않았다.

    마지막은 어차피 무한 re-rendering은 막았으니까 맨 마지막 자식에서 상태를 바꿔보자 해서 제품 카드에서 setItems를 context를 통해 보내 useEffect안에서 사용했다.

    useEffect(() => {
      setTotalPrice(count * product.price);
      setItems([...items]);
    }, [count, product.price]);

    제품에서 count를 통해 제품이 바뀌면 useEffect가 다시 실행이 되니 업데이트 된 items의 정보를 담아 최상단 부모에서 가져온 setItems를 이용해 전체적인 items의 상태를 바꿀 수 있었다.

  2. 전체 비우기

    의외로 어렵다. 전체 items를 비워야하는데 그곳에 later가 1인 것은 또 남겨둬야한다.

    const clearBasket = () => {
      setIsNone(true);
      setItems([...items.filter(i => i.later !== 0)]);
    };

    먼저 조건문에 totalCounts()라는 함수를 이용하기 때문에 그냥 바로 setItems만 바꾸면 items가 없어지기 때문에 오류가 나게 된다.
    따라서 우선 화면을 바꿔주고 난 후에 items를 바꿔준다.

    그 후 filter를 이용해 items를 later가 0이 아닌것들만 남겨두려는데 자꾸 빈배열로 바뀐다.
    왜 그런지 계속 코드를 보다가 좀 더 직관적이게 작성한 코드가 문제였다.

let { items, setItems } = useContext(CartContext);

const totalCounts = () => {
items = items.filter(i => i.later === 0)
  let counts = items.map(i => i.count);
  return counts.reduce((a, c) => a + c);
};

장바구니의 위치에선 items의 later가 0인 data들만 놔두기 위해 useContext에서 가져온 값들을 let으로 설정했었는데 이게 자꾸 items 자체를 바꿔서 발생하는 문제였다.

따라서 useContext를 통해 가져올때 선언문을 const로 바꾸고 method들을 엮었다.

const totalCounts = () => {
  let counts = items.filter(i => i.later === 0).map(i => i.count);
  return counts.reduce((a, c) => a + c);
};



느낀점

생각보다 어려웠던것 같다 아직 나중에 쇼핑하기 컴포넌트도 약간 더 손을 봐야하고 장바구니에 담을때 obj들에 later라는 key를 어떻게 넣을것인가도 고민해봐야할 문제인것같다.

일주일 동안 고민할점이 많아 공부할게 많아진거 같아서 좀 고통스러웠지만 재밌었다.
프론트에 재미를 느끼는게 확실한것 같아 진로에 대한 결심이 프론트로 약간 더 기울여진것 같다.

0개의 댓글