Jieunny์˜ ๋ธ”๋กœ๊ทธ

S3) Unit 4. [์‹ค์Šต] CMarket Redux ๋ณธ๋ฌธ

CodeStates/Training

S3) Unit 4. [์‹ค์Šต] CMarket Redux

Jieunny 2023. 2. 27. 14:16

๐Ÿ“ฃ  CMarket์˜ ์ƒํƒœ๋ฅผ Redux๋กœ ๊ด€๋ฆฌํ•ด๋ณด์ž

  โœ”๏ธ Shopping Cart Actions
    โœ“ addToCart๋Š” ADD_TO_CART ์•ก์…˜์„ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (4 ms)
    โœ“ removeFromCart๋Š” REMOVE_FROM_CART ์•ก์…˜์„ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (13 ms)
    โœ“ setQuantity๋Š” SET_QUANTITY ์•ก์…˜์„ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (2 ms)


  โœ”๏ธ Item Reducer
    โœ“ ADD_TO_CART ์•ก์…˜์— ๋”ฐ๋ผ cartItems ์ƒํƒœ๊ฐ€ ๋ณ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (4 ms)
    โœ“ REMOVE_FROM_CART ์•ก์…˜์— ๋”ฐ๋ผ cartItems ์ƒํƒœ๊ฐ€ ๋ณ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (2 ms)
    โœ“ SET_QUANTITY ์•ก์…˜์— ๋”ฐ๋ผ cartItems ์ƒํƒœ๊ฐ€ ๋ณ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (2 ms)
    โœ“ ๋ฆฌ๋“€์„œ๋Š” ๋‹ค๋ฅธ ์ƒํƒœ์˜ ๊ฐ’์„ ๋ณด์กดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค (2 ms)
 

โœ”๏ธ Shopping Pages

    โœ“ ShoppingCart์— cartItems๊ฐ€ ๋ Œ๋”๋˜์–ด์•ผํ•ฉ๋‹ˆ๋‹ค. (134 ms)
    โœ“ ADD_TO_CART ์•ก์…˜์— ๋”ฐ๋ผ ShoppingCart๊ฐ€ ๋ Œ๋”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (103 ms)
    โœ“ REMOVE_FROM_CART ์•ก์…˜์— ๋”ฐ๋ผ ShoppingCart๊ฐ€ ๋ Œ๋”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (31 ms)
    โœ“ SET_QUANTITY ์•ก์…˜์— ๋”ฐ๋ผ OrderSummary๊ฐ€ ๋ Œ๋”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (26 ms)
    โœ“ Checkbox์˜ ์ƒํƒœ์— ๋”ฐ๋ผ OrderSummary๊ฐ€ ๋ Œ๋”๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (176 ms)


๐Ÿ“ฃ  ๊ตฌํ˜„ ์ฝ”๋“œ

โœ”๏ธ InitialState ์—๋Š” ์ „์ฒด ์•„์ดํ…œ 'items'์™€ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ๋‹ด๊ธด ์•„์ดํ…œ 'cartItems' ์ด ๋‹ด๊ฒจ์žˆ๋‹ค.

 

1๏ธโƒฃ 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: {
      quantity: 1,
      itemId
    }
  }
}

export const removeFromCart = (itemId) => {
  return {
    //TODO
    type: REMOVE_FROM_CART,
    payload: {
      itemId: itemId
    }
  }
}

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

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
  }
}

โžฐ ์•ก์…˜ ํ•จ์ˆ˜์—๋Š” ์–ด๋–ค ์•ก์…˜์„ ์‹คํ–‰ํ•  ๊ฑด์ง€์— ํ•ด๋‹นํ•˜๋Š” type๊ณผ, ํ•ด๋‹น ์•ก์…˜๊ณผ ๊ฐ™์ด ์ „๋‹ฌํ•  payload๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

โžฐ payload ๋Š” ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ค ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ  ์ž‘์„ฑํ•˜๋ฉด ์ข‹๋‹ค.

 

2๏ธโƒฃ itemReducer.js

โœ”๏ธ ์•ก์…˜์„ ๋ฆฌ๋“€์„œ์— ์ „๋‹ฌํ•˜๊ณ , ๋ฆฌ๋“€์„œ๋Š” ์ด๋ฅผ ๋ณด๊ณ  ์Šคํ† ์–ด์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.

โœ”๏ธ ํ˜„์žฌ์˜ ์ƒํƒœ์™€ ์ „๋‹ฌ๋ฐ›์€ ์•ก์…˜์„ ์ฐธ๊ณ ํ•ด์„œ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค -> ์•ก์…˜์— ๋”ฐ๋ผ ์ƒํƒœ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณ€ํ™”์‹œ์ผœ์„œ ๋ฐ˜ํ™˜ํ• ๊ฑด์ง€!

import { REMOVE_FROM_CART, ADD_TO_CART, SET_QUANTITY } from "../actions/index";
import { initialState } from "./initialState";

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

  switch (action.type) {
    case ADD_TO_CART:
      return Object.assign({}, state, {
          cartItems: [...state.cartItems, action.payload]
        })
        // ADD_TO_CART ์•ก์…˜์€ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์•„์ดํ…œ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
        // state์—๋Š” ๊ธฐ๋ณธ ์•„์ดํ…œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ๋„ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค.
        // ์›๋ž˜ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ๊ณผ ์ „๋‹ฌ๋ฐ›์€ ์•„์ดํ…œ๋„ ์ถ”๊ฐ€ํ•ด์„œ cartItems๋ฅผ ๊ฐฑ์‹ ํ•˜๊ณ 
        // assign์„ ์จ์„œ ์›๋ž˜ state์™€ ํ•ฉ์นœ๋‹ค.
        // ๊ทธ๋Ÿฌ๋ฉด ์›๋ž˜ state์˜ cartItems๊ฐ€ ์ƒˆ๋กœ ๊ฐฑ์‹ ๋œ cartItems๋กœ ๋ฎ์–ด์“ฐ์›Œ์ง„๋‹ค.

      break;
    case REMOVE_FROM_CART:
      //TODO
      let removedCartItems = state.cartItems.filter((cartItem) => cartItem.itemId !== action.payload.itemId);
      return Object.assign({}, state, {
        cartItems: removedCartItems
      })
      // REMOVE_FROM_CART ์•ก์…˜์€ ์žฅ๋ฐ”๊ตฌ๋‹ˆ์—์„œ ์•„์ดํ…œ์„ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
      // ๋ณ€์ˆ˜ ํ•˜๋‚˜๋ฅผ ์„ ์–ธํ•ด์„œ ์‚ญ์ œํ•  ์•„์ดํ…œ์ด ์ œ์™ธ๋œ ๋ฐฐ์—ด์„ ๋„ฃ๋Š”๋‹ค.
      // ์›๋ž˜ state์˜ cartItems์— filter ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ „๋‹ฌ๋ฐ›์€ itemId์™€ ๋‹ค๋ฅธ itemId๋ฅผ ๊ฐ€์ง„ ์•„์ดํ…œ๋“ค๋งŒ ๊ฐ€์ ธ์˜จ๋‹ค.
      // ADD_TO_CART์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ cartItems๋ฅผ ์ƒˆ๋กœ ์„ ์–ธํ•œ ๋ณ€์ˆ˜๋กœ ๊ฐฑ์‹ ํ•˜๊ณ , assign์œผ๋กœ ๋ฎ์–ด์”Œ์šด๋‹ค.
      
      break;
    case SET_QUANTITY:
      let idx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId)
      //TODO
	  return Object.assign({}, state, {
        cartItems: [...state.cartItems.slice(0, idx), action.payload, ...state.cartItems.slice(idx+1)]
       })
      // ์ˆ˜๋Ÿ‰์„ ์ฆ๊ฐ€์‹œ์ผœ์•ผ ํ•  ์•„์ดํ…œ์˜ idx๊ฐ€ ์ด๋ฏธ ๊ตฌํ•ด์ ธ์žˆ๋‹ค.
      // cartItems์„ 0๋ถ€ํ„ฐ idx๊นŒ์ง€ ์ž๋ฅธ ๋ฐฐ์—ด, ์ˆ˜๋Ÿ‰์ด ๋ณ€ํ•œ payload, idx๋’ค์˜ ๋ฐฐ์—ด์„ ํ•ฉ์ณ์„œ cartItems๋ฅผ ๊ฐฑ์‹ ํ•ด์ค€๋‹ค.
      // ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋‚ด๊ฐ€ ์ˆ˜๋Ÿ‰์„ ๋ฐ”๊พธ๊ณ  ์‹ถ์€ ์•„์ดํ…œ์˜ ์ˆ˜๋Ÿ‰๋งŒ ๋ฐ”๋€Œ์–ด์žˆ๋‹ค.

      break;
    default:
      return state;
  }
}

export default itemReducer;

 

 

3๏ธโƒฃ ItemListContainer.js

โœ”๏ธ ์•ก์…˜์„ ๋ฆฌ๋“€์„œ์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” dispatch() ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

import React from 'react';
import { addToCart, notify } from '../actions/index';
import { useSelector, useDispatch } from 'react-redux';
import Item from '../components/Item';

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)) {
      //TODO: dispatch ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์•„์ดํ…œ ์ถ”๊ฐ€์— ๋Œ€ํ•œ ์•ก์…˜์„ ์ „๋‹ฌํ•˜์„ธ์š”.
      dispatch(addToCart(item.id));
      dispatch(notify(`์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ${item.name}์ด(๊ฐ€) ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`))
    }
    else {
      dispatch(notify('์ด๋ฏธ ์ถ”๊ฐ€๋œ ์ƒํ’ˆ์ž…๋‹ˆ๋‹ค.'))
    }
  }

  return (
    <div id="item-list-container">
      <div id="item-list-body">
        <div id="item-list-title">์“ธ๋ชจ์—†๋Š” ์„ ๋ฌผ ๋ชจ์Œ</div>
        {items.map((item, idx) => <Item item={item} key={idx} handleClick={() => {
          handleClick(item)
        }} />)}
      </div>
    </div>
  );
}

export default ItemListContainer;

โžฐ dispatch ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์•„์ดํ…œ ์ถ”๊ฐ€์— ๋Œ€ํ•œ ์•ก์…˜์„ ์ „๋‹ฌํ•œ๋‹ค.

โžฐ dispatch์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ „๋‹ฌํ•  ์•ก์…˜ ํ•จ์ˆ˜๋ฅผ ์ค€๋‹ค.

โžฐ ๊ทธ ์•ก์…˜ํ•จ์ˆ˜๋Š” ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋ฐ›๋Š”๋‹ค.

 

4๏ธโƒฃ ShoppingCart.js

import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { removeFromCart, setQuantity } from '../actions';
import CartItem from '../components/CartItem'
import OrderSummary from '../components/OrderSummary'

export default function ShoppingCart() {

  const state = useSelector(state => state.itemReducer);
  const { cartItems, items } = state
  const dispatch = useDispatch();
  const [checkedItems, setCheckedItems] = useState(cartItems.map((el) => el.itemId))

  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) => {
    //TODO: dispatch ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์•ก์…˜์„ ์ „๋‹ฌํ•˜์„ธ์š”.
    dispatch(setQuantity(itemId, quantity));
  }

  const handleDelete = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId))
    //TODO: dispatch ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์•ก์…˜์„ ์ „๋‹ฌํ•˜์„ธ์š”.
    dispatch(removeFromCart(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 (
    <div id="item-list-container">
      <div id="item-list-body">
        <div id="item-list-title">์žฅ๋ฐ”๊ตฌ๋‹ˆ</div>
        <span id="shopping-cart-select-all">
          <input
            type="checkbox"
            checked={
              checkedItems.length === cartItems.length ? true : false
            }
            onChange={(e) => handleAllCheck(e.target.checked)} >
          </input>
          <label >์ „์ฒด์„ ํƒ</label>
        </span>
        <div id="shopping-cart-container">
          {!cartItems.length ? (
            <div id="item-list-text">
              ์žฅ๋ฐ”๊ตฌ๋‹ˆ์— ์•„์ดํ…œ์ด ์—†์Šต๋‹ˆ๋‹ค.
            </div>
          ) : (
              <div id="cart-item-list">
                {renderItems.map((item, idx) => {
                  const quantity = cartItems.filter(el => el.itemId === item.id)[0].quantity
                  return <CartItem
                    key={idx}
                    handleCheckChange={handleCheckChange}
                    handleQuantityChange={handleQuantityChange}
                    handleDelete={handleDelete}
                    item={item}
                    checkedItems={checkedItems}
                    quantity={quantity}
                  />
                })}
              </div>
            )}
          <OrderSummary total={total.price} totalQty={total.quantity} />
        </div>
      </div >
    </div>
  )
}

โžฐ 3๋ฒˆ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ์•„์ดํ…œ ์‚ญ์ œ ์•ก์…˜, ์ˆ˜๋Ÿ‰ ๋ณ€๊ฒฝ ์•ก์…˜์„ dispatch๋กœ ์ „๋‹ฌํ•ด์ค€๋‹ค.

 

โœ”๏ธ ์‹œ์—ฐํ™”๋ฉด์€ CMarket Hooks๊ณผ ๊ฐ™๋‹ค!