Redux - Cmarket

ock·2023년 2월 27일
0

itemReducer.js


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

const itemReducer = (state = initialState, action) => {
//initialState 객체를 초기 상태로 설정하고, 
//ADD_TO_CART, REMOVE_FROM_CART, SET_QUANTITY 세 가지 액션 타입에 대한 처리를 구현.
  switch (action.type) {
    case ADD_TO_CART:
      //cartItems 배열에 action.payload 객체를 추가하여 새로운 배열을 생성하고, 
      //이 배열을 이용하여 상태를 업데이트하는 것을 구현. 
      //...state 문법은 이전 상태(state) 객체를 복사한 것. 
      //TODO

      return {
        ...state,
        cartItems: [...state.cartItems, action.payload]
      }
      
      break;
    case REMOVE_FROM_CART:
      //action.payload.itemId와 일치하는 cartItems 배열 요소를 제거하여 
      //새로운 배열을 생성하고, 이 배열을 이용하여 상태를 업데이트하는 것을 구현. 
      //state.cartItems.filter 함수는 state.cartItems 배열에서 
      //el.itemId 값이 action.payload.itemId와 일치하지 않는 모든 요소를 골라서 
      //새로운 배열로 만든다. 
      //이 새로운 배열을 새로운 상태로 반환한다.
      //TODO
      
      return {
        ...state,
        cartItems: state.cartItems.filter(el => el.itemId !== action.payload.itemId)
      }
      
      break;
    case SET_QUANTITY:
      let targetIdx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId)
      //action.payload.itemId와 일치하는 cartItems 배열 요소를 찾아서 
      //action.payload로 대체하고, 이 배열을 이용하여 상태를 업데이트하는 것을 구현.
      //state.cartItems.findIndex 함수는 state.cartItems 배열에서 
      //el.itemId 값이 action.payload.itemId와 일치하는 첫 번째 요소의 인덱스를 반환한다. 
      //이후 state.cartItems.map 함수를 사용하여 각 요소에 대해서 인덱스를 확인하고, 
      //만약 targetIdx와 같은 인덱스를 가진 요소가 있다면 action.payload로 대체한다. 
      //그 외의 경우에는 그대로 반환한다.
      //TODO
      
      return {
        ...state,
        cartItems:state.cartItems.map((el,idx)=>{
          if(idx === targetIdx){
            return action.payload;
          }else{
            return el;
          }
        })
      }

      break;
    default:
      return state;
      //액션 타입이 지정되어 있지 않을 때 기본적으로 이전 상태를 반환.
  }
}

export default itemReducer;

//최종적으로 itemReducer 함수는 업데이트된 상태를 반환하고, 
//다른 코드에서 combineReducers 함수를 이용하여 
//다른 Reducer 함수들과 함께 하나의 Root Reducer로 결합된다.



action.payload

action.payload는 Redux store에서 발생한 액션 객체(Action Object)의 payload 프로퍼티 값이다. 액션 객체는 Redux store에서 상태(state)를 변경하기 위해 발생하는 이벤트이다.
액션 객체는 일반적으로 다음과 같은 형태로 구성된다.


{
  type: "액션 타입",
  payload: "액션에 필요한 값"
}

action.payload
여기서 type은 액션의 종류를 나타내는 문자열 값이며, payload는 액션 처리에 필요한 추가적인 정보를 나타내는 값이다.
따라서 action.payload는 type 외에 추가적인 정보를 전달하는 역할을 한다.





case ADD_TO_CART


case ADD_TO_CART:

return {
        ...state,
        cartItems: [...state.cartItems, action.payload]
      }

ADD_TO_CART 액션이 발생하면, 새로운 상품을 장바구니에 추가하는 기능을 구현한다.

액션 객체인 action.payload에는 추가하려는 상품 정보가 포함되어 있다. 이 정보를 이용해 cartItems 배열에 새로운 상품을 추가한다.

cartItems 배열에 새로운 상품을 추가하기 위해서는, ...state.cartItems를 사용해서 이전의 cartItems 배열을 복사하고, 그 뒤에 action.payload를 추가한 새로운 배열을 만든다.

마지막으로, 업데이트된 cartItems 배열과 기존의 state 객체를 합쳐서 새로운 상태 객체를 반환한다.





case REMOVE_FROM_CART


case REMOVE_FROM_CART:

return {
        ...state,
        cartItems: state.cartItems.filter(el => el.itemId !== action.payload.itemId)
      }

REMOVE_FROM_CART 액션이 발생했을 때, 현재 장바구니에서 지우려는 상품을 제거하는 기능을 구현한다.

액션 객체인 action.payload에는 제거하려는 상품의 아이디(itemId)가 포함되어 있다. 이 정보를 이용해 cartItems 배열에서 제거하려는 상품을 찾는다.

cartItems 배열의 filter 메서드를 이용해서, itemId가 action.payload.itemId와 일치하지 않는 모든 요소를 골라서 새로운 배열로 만든다.
이 새로운 배열은 기존의 cartItems 배열에서 action.payload.itemId와 일치하는 상품이 제거된 배열이 된다.

마지막으로, 업데이트된 cartItems 배열과 기존의 state 객체를 합쳐서 새로운 상태 객체를 반환한다.

{
  ...state,
  cartItems: [/* 제거된 요소가 제외된 cartItems 배열 */]
}




case SET_QUANTITY


case SET_QUANTITY:

let targetIdx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId)

      return {
        ...state,
        cartItems:state.cartItems.map((el,idx)=>{
          if(idx === targetIdx){
            return action.payload;
          }else{
            return el;
          }
        })
      }

SET_QUANTITY 액션이 발생했을 때, 현재 장바구니에 있는 상품의 수량(quantity)을 업데이트하는 기능을 구현한다.

액션 객체인 action.payload에는 업데이트하려는 상품의 아이디(itemId)와 수량(quantity) 정보가 포함되어 있다. 이 정보를 이용해 기존의 cartItems 배열에서 업데이트할 상품의 인덱스(targetIdx)를 찾는다.

이후 cartItems 배열의 각 요소를 확인하면서, 현재 요소의 인덱스(idx)가 targetIdx와 일치하면 해당 요소를 업데이트한다. 업데이트된 요소는 action.payload로 대체하고, 그 외의 요소는 그대로 둔다.

마지막으로, 업데이트된 cartItems 배열과 기존의 state 객체를 합쳐서 새로운 상태 객체를 반환한다.

{
  ...state,
  cartItems: [/* 업데이트된 cartItems 배열 */]
}




index.js(Action)


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

export const setQuantity = (itemId, quantity) => {
  return {
    //TODO
    type: SET_QUANTITY,
    payload: { 
    itemId,
    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
  }
}




shoppingCart.js(dispatch(액션))


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))
    //인자 순서가 바뀌면 안되는 이유
    //setQuantity 액션에서 payload 객체의 프로퍼티에 
    //itemId와 quantity를 각각 할당하기 때문
    //setQuantity 액션 함수는 itemId와 quantity를 매개변수로 받아서, 
    //payload 객체를 생성하고 이를 type과 함께 액션 객체로 반환한다. 
    //따라서 setQuantity(itemId, quantity) 호출시 
    //itemId와 quantity의 순서가 바뀌면,
    //payload 객체의 프로퍼티도 바뀌어서 기존에 의도했던대로 동작하지 않을 수 있다.
    //예를 들어, setQuantity(itemId, quantity) 대신에 
    //setQuantity(quantity, itemId)와 같이 순서를 바꾼다면, 
    //payload 객체가 { itemId: quantity, quantity: itemId }와 같이 생성될 수 있다. 
    //이 경우, itemId와 quantity의 값을 잘못 할당받기 때문에, 
    //state 객체를 업데이트하는 과정에서 문제가 발생할 수 있다. 
    //따라서 인자의 순서를 바꾸지 않도록 주의해야 한다.
  }

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





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;




profile
어제의 ock보다 성장한 오늘의 ock_FE 개발자

0개의 댓글