Cmarket-Hooks, Cmarket-Redux

선유준·2023년 2월 27일
0

BOOTCAMP

목록 보기
6/11

✅ Cmarket-Hooks

App.js

import React, { useState } from 'react';
import Nav from './components/Nav';
import ItemListContainer from './pages/ItemListContainer';
import './App.css';
import './variables.css';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import ShoppingCart from './pages/ShoppingCart';
import { initialState } from './assets/state';

function App() {
  const [items, setItems] = useState(initialState.items); //데이터를 담은 배열
  const [cartItems, setCartItems] = useState(initialState.cartItems); //장바구니 리스트를 담은 배열

  const addCartList = (itemId) => {
    const foundId = cartItems.filter(el => el.itemId === itemId)[0]; // 중복되는 요소가 있는지 확인 (값은 최대 1개이니 바로 요소를 꺼내준다)

    if (foundId) {
      setQuantity(itemId, foundId.quantity + 1)
    } else {
      setCartItems([...cartItems, {itemId:itemId, quantity: 1}])
    }
  }

  const setQuantity = (itemId, quantity) => { // 수량을 증가시켜준 후, 그 요소를 찾아 카트아이템에 집어넣어주는 함수

    const foundId = cartItems.filter((el) => el.itemId === itemId)[0]; 
    const idx = cartItems.indexOf(foundId); 
    const cartItem = { //   quantity가 증가하여 다시 재정의 해준다. {itemId: itemId , quantity: quantity} 받아온 인자와 이름이 같기떄문에 아래와 같이 작성
      itemId,
      quantity,
    };
    setCartItems([...cartItems.slice(0, idx), cartItem, ...cartItems.slice(idx + 1)]); // 덮어씌우기
    console.log(foundId);

  };

							...
                         return 생략

ShoppingCart.js

import React, { useState } from 'react'
import CartItem from '../components/CartItem'
import OrderSummary from '../components/OrderSummary'
import { Link } from 'react-router-dom';

export default function ShoppingCart({ items, cartItems, setCartItems }) {
  const [checkedItems, setCheckedItems] = useState(
    cartItems.map((el) => el.itemId)
  ); //카트아이템의 id값을 요소로하는 배열

  const handleCheckChange = (checked, id) => {
    //체크된 리스트를 추가, 제거하는 함수
    if (checked) {
      setCheckedItems([...checkedItems, id]);
    } else {
      setCheckedItems(checkedItems.filter((el) => el !== id));
    }
  };

  const handleAllCheck = (checked) => {
    //모두선택 체크박스를 선택하면 모두선택되고, 다시누르면 모두 해체
    if (checked) {
      setCheckedItems(cartItems.map((el) => el.itemId));
    } else {
      setCheckedItems([]);
    }
  };

  const handleQuantityChange = (quantity, itemId) => {
    let obj = cartItems.filter(el => el.itemId === itemId)[0];
    let idx = cartItems.indexOf(obj);
    obj = {
      itemId,
      quantity,
    }
    setCartItems([...cartItems.slice(0, idx), obj, ...cartItems.slice(idx + 1)]);
  };

  const handleDelete = (itemId) => {
    //지우는 함수
    setCheckedItems(checkedItems.filter((el) => el !== itemId)); //체크박스 상태를 false로 함
    setCartItems(cartItems.filter(el => el.itemId !== itemId)); // 상품을 제거한다.
  };

  const getTotal = () => {
    let cartIdArr = cartItems.map((el) => el.itemId);
    let total = {
      price: 0,
      quantity: 0,
    };
    for (let i = 0; i < cartIdArr.length; i++) {
      if (checkedItems.indexOf(cartIdArr[i]) > -1) {
        let quantity = cartItems[i].quantity;
        let price = items.filter((el) => el.id === cartItems[i].itemId)[0]
          .price;

        total.price = total.price + quantity * price;
        total.quantity = total.quantity + quantity;
      }
    }
    return total;
  };

  const renderItems = items.filter(
    (el) => cartItems.map((el) => el.itemId).indexOf(el.id) > -1
  );
  const total = getTotal();

							...
                         return 생략

Cmarket에서 구현한 기능

1. 장바구니에 추가 및 상품 개수 업데이트

  • 메인 화면에서 [장바구니 담기] 버튼을 누른 후, 장바구니 페이지로 이동하면 상품이 담겨있어야 합니다.

  • 장바구니 페이지에서 장바구니에 담긴 각 아이템의 개수를 변경할 수 있어야 합니다.

  • 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.

2. 장바구니로부터 제거

  • 장바구니 페이지에서 [삭제] 버튼을 누른 후, 해당 상품이 목록에서 삭제되어야 합니다.

  • 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.

  • 상품 목록 (items)
{
  "id": 1,
  "name": "노른자 분리기",
  "img": "../images/egg.png",
  "price": 9900
}
...
  • 장바구니 목록(cartItems)
{
  "itemId": 1,
  "quantity": 1
}
...

먼저 장바구니에 상품을 추가하려면 장바구니 목록 데이터를 이용해야한다.

아래의 함수를 이용하여 선택한 상품의 id를 인자로 전달하여 함수를 실행시킨다.

ItemListContainer

function ItemListContainer({ items, addCartList }) {
  const handleClick = (e, id) => {
    addCartList(id); // App.js에서 props로 전달 받은 함수
  };

이후 전달받은 id를 이용하여 cartItems 상태에 값을 추가해야하지만,
이미 있는 상품이라면 상품을 추가하지않고 수량만 증가시켜야한다.

foundId 라는 변수를 선언하여 중복되는 요소를 확인하는 용도로 사용하였다.

addCartList

const addCartList = (itemId) => {
    const foundId = cartItems.filter(el => el.itemId === itemId)[0]; // 중복되는 요소가 있는지 확인 (값은 최대 1개이니 바로 요소를 꺼내준다)

    if (foundId) {
      setQuantity(itemId, foundId.quantity + 1) // 수량을 하나 증가시킨다
    } else {
      setCartItems([...cartItems, {itemId:itemId, quantity: 1}])
    }
  }

데이터의 수량만 증가시키고 싶을 때, index를 찾아서 아래와 같이 slice 메서드를 이용해 cartItems를 덮어씌우면 된다.

setQuantity

 const setQuantity = (itemId, quantity) => { // 수량을 증가시켜준 후, 그 요소를 찾아 카트아이템에 집어넣어주는 함수

    const foundId = cartItems.filter((el) => el.itemId === itemId)[0]; 
    const idx = cartItems.indexOf(foundId); 
    const cartItem = { //   quantity가 증가하여 다시 재정의 해준다. {itemId: itemId , quantity: quantity} 받아온 인자와 이름이 같기떄문에 아래와 같이 작성
      itemId,
      quantity,
    };
    setCartItems([...cartItems.slice(0, idx), cartItem, ...cartItems.slice(idx + 1)]); // 덮어씌우기
    console.log(foundId);
  };

내비게이션 바에 상품개수를 표시하는 방법은 cartItems의 길이를 이용하면 된다.

<span id="nav-item-counter">{cartItems.length}</span>

장바구니 페이지에서 수량을 증가 / 감소 시키는 것은 위의 setQuantity와 똑같은 방법으로 하면 된다.


✅ Cmarket-Redux

위의 Cmarket-HooksRedux를 이용하여 리팩토링 하는 과제이다.

itemReducer : 현재의 state와 Action을 이용해서 새로운 state를 만들어 내는 순수함수이다.

import { REMOVE_FROM_CART, ADD_TO_CART, SET_QUANTITY } from "../actions/index";
import { initialState } from "./initialState"; // 객체이며 cartItems: [...], items:[...] 을 가지고 있다.

const itemReducer = (state = initialState, action) => {

  switch (action.type) {
    case ADD_TO_CART:
      return Object.assign({}, state, { // 불변성을 지키기 위해 빈 객체에 값들을 집어넣어 새로운 객체를 반환한다.
        cartItems: [...state.cartItems, action.payload], // 기존의 cartItems을 덮어씌운다.
      });
    
    case REMOVE_FROM_CART:
      console.log(action.payload);
      return Object.assign({}, state, {
        cartItems: state.cartItems.filter(el => el.itemId !== action.payload.itemId) //선택한 리스트만 제거된 배열로 재정의해줌
      })
    
    case SET_QUANTITY:
      let idx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId)

      return Object.assign({}, state, {
         cartItems: [...state.cartItems.slice(0, idx), action.payload, ...state.cartItems.slice(idx + 1)]
      })

    default:
      return state;
  }
}

export default itemReducer;

action / index.js ( 액션을 만드는 함수들 )

// action types
export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const SET_QUANTITY = "SET_QUANTITY";
export const NOTIFY = "NOTIFY";
export const ENQUEUE_NOTIFICATION = "ENQUEUE_NOTIFICATION";
export const DEQUEUE_NOTIFICATION = "DEQUEUE_NOTIFICATION";

// actions creator functions
export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: {
      itemId,
      quantity: 1,
    },
  };
};

export const removeFromCart = (itemId) => {
  return {
    type: REMOVE_FROM_CART,
    payload: { itemId }, //reducer는 기존 상태를 보존해야하기 때문에 객체형태로 작성
  };
};

export const setQuantity = (itemId, quantity) => {
  return {
    type: SET_QUANTITY,
    payload: {
      itemId,
      quantity,
    },
  };
};

export const notify = function (message, dismissTime = 5000) {
  return function (dispatch) {
    const uuid = Math.random();
    dispatch(enqueueNotification(message, dismissTime, uuid));
    setTimeout(() => {
      dispatch(dequeueNotification());
    }, dismissTime);
  };
};

//위의 코드는 이런 방식으로도 표현 가능

// export const notify = (message, dismissTime = 5000) => dispatch => { //함수 중첩문
//   const uuid = Math.random()
//   dispatch(enqueueNotification(message, dismissTime, uuid))
//   setTimeout(() => {
//     dispatch(dequeueNotification())
//   }, dismissTime)
// }

export const enqueueNotification = (message, dismissTime, uuid) => {
  return {
    type: ENQUEUE_NOTIFICATION,
    payload: {
      message,
      dismissTime,
      uuid,
    },
  };
};

export const dequeueNotification = () => {
  return {
    type: DEQUEUE_NOTIFICATION,
  };
};

장바구니에 추가 및 상품 개수 업데이트

위의 이미지에는 state, items, cartItems 변수의 값이 순서대로 나와있다.

리스트를 클릭했을 때, 중복이 아니라면 addToCart함수에 id를 인자로 주어 실행시킨다.

function ItemListContainer() {
  const state = useSelector(state => state.itemReducer);
  const { items, cartItems } = state; 
  const dispatch = useDispatch();

  const handleClick = (item) => {
    if (!cartItems.map((el) => el.itemId).includes(item.id)) {
      dispatch(addToCart(item.id))
      dispatch(notify(`장바구니에 ${item.name}이(가) 추가되었습니다.`)) //message를 인자로 전달
    }
    else {
      dispatch(notify('이미 추가된 상품입니다.'))
    }
  }
  ...

Action 생성 함수

export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: {
      itemId,
      quantity: 1,
    },
  };

위의 Cmarket-Hooks와는 다르게 actions.payload를 이용하여 Action 생성함수에서 객체를 가져와서 사용한다.

나머지 기능구현은 Cmarket-HookJavaScript코드는 거의 똑같기 때문에, action, reducer, dispatch, store 가 어떻게 유기적으로 연결되어 있는지만 이해하면 좋을듯하다.

profile
매일매일 발전하는 개발자를 목표로!

0개의 댓글