[06.23] 과제_Cmarket redux Review

0
post-thumbnail

과제 개요 및 목표

  • 과제를 하면서 Action, Dispatch, Reducer, Store가 어떻게 연결되어 있고 어떤 과정으로 구현되는지 확인하기
  • Action, Reducer르 직접 작성하고 테스트 해보기
  • 지난번 react hooks로 작성한 페이지를 redux를 사용해서 리팩토링

상태 관리 순서

📍 Action ➡️ Dispatch ➡️ Reducer ➡️ Store

  1. 상태 변경 이벤트 발생 ➡️ 변경될 상태 Action 객체 생성
  2. Action 객체가 Dispatch에게 전달
  3. Dispatch 함수가 Action 객체를 Reducer 함수로 전달
  4. Reducer 함수는 Action 객체를 확인하고 전역 상태 저장소인 Store의 상태를 변경
  5. 상태 변경되면 렌더링

Action(index.js)

... 생략

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

...생략

Dispatch(ItemListContainer.js)

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

function ItemListContainer() {
//itemReducer의 상태만 선택해서 가져다줘
const state = useSelector(state => state.itemReducer);
const { items, cartItems } = state;
const dispatch = useDispatch();

const handleClick = (item) => {
  // 아이템 페이지에서 클릭된 아이템이 id가 장바구니에 포함되어 있지 않다면 아이템을 추가해줘!
  if (!cartItems.map((el) => el.itemId).includes(item.id)) {
    //TODO: dispatch 함수를 호출하여 아이템 추가에 대한 액션을 전달하세요.
    dispatch(addToCart(item.id))
    dispatch(notify(`장바구니에 ${item.name}이(가) 추가되었습니다.`))
  }
  else {
    dispatch(addToCart(item.id))
    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(ShoppinCart.js)

... 생략
      
  const handleQuantityChange = (quantity, itemId) => {
    //TODO: dispatch 함수를 호출하여 액션을 전달하세요.
    dispatch(setQuantity(itemId,quantity))
    
  }

  const handleDelete = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId))
    //TODO: dispatch 함수를 호출하여 액션을 전달하세요.
    dispatch(removeFromCart(itemId))
  }
      
...생략

Reducer(itemReducer.js)

📍 새로 알게된 내용

  • Object.assign(targer, sources) : 하나 이상의 객체를 복사하는 것
    - 원본을 건들이지 않는 복사
    - 동일한 key가 있으면 뒤에 있는 key값이 먼저가 됨
      // 새로운 state를 만들어서 기존의 state와 장바구니에 액션을 통해 
      // 추가되어지는 아이템을 객체에 담아 리턴함
      // 3번째 인자에는 변경해주고 싶은 값을 전달
      else {
      return Object.assign( {}, state, {
      cartItems: [...state.cartItems, action.payload]
      })
  • Object.assign()을 대신하는 Spread syntax 등장
      else {
        return {
      	// 기존 배열을 복사
          ...state,
      	// 카트아이템 복사와 액션에 따른 상태를 구현
          cartItems: [...state.cartItems, action.payload]
        }
      }

[전체코드]

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

//state는 초기상태의 배열을 담고 있다
const itemReducer = (state = initialState, action) => {

  switch (action.type) {
    case ADD_TO_CART:
      // 장바구니에 있는 itemId과 액션이 발생했을때의 itemID이 동일하다면 해당 index를 찾음 
      let existItem = state.cartItems.findIndex(el => el.itemId === action.payload.itemId)
      // 해당 인덱스가 없다면 양만 늘려줘
      if(existItem !== -1) {
        // 상태값을 직접 건드리면 안되서 복사를 하기
        let newcartItems = [...state.cartItems]
        // 장바구니의 특정 인덱스의 수량을 늘려주기
        newcartItems[existItem].quantity += 1
        return {
          ...state,
          cartItems : newcartItems
        }
      }
      else {
        return {
          ...state,
          cartItems: [...state.cartItems, action.payload]
        }
      }
      break;

    case REMOVE_FROM_CART:
      // 삭제하려고 하는 아이템 외의 아이템들만 걸러줘
      let deleteItem = state.cartItems.filter((el) => el.itemId !== action.payload.itemId)
      return Object.assign({}, state, {
        cartItems: deleteItem
      })
      break;

    case SET_QUANTITY:
      // 상태에 있는 카트아이템에서 액션이 발생되어지는(상태가 변화) 동일한 itemId의 값을 idx에 담아줘
      let idx = state.cartItems.findIndex(el => el.itemId === action.payload.itemId)
      // 카트아이템 배열 복사
      let newQuantity = [...state.cartItems]
      // 배열의 idx의 quantity 수량을 액션의 상태에 맞게 바꿔줘
      newQuantity[idx].quantity  = action.payload.quantity
      console.log(newQuantity)
      return  {
        ...state,
        cartItems: newQuantity
        // [레퍼런스]
        // 1. 카트 아이템에서 0부터 인덱스 전까지의 요소들로 이루어진 배열 생성
        // 2. 액션 객체에서 전달된 데이터로 카트 아이템의 수량이 포함됨
        // 3. 카트 아이템에서 배열에서 인덱스부터 마지막 인덱스까지의 요소들로 이루어진 배열생성
        // [...state.cartItems.slice(0,idx), action.payload, ...state.cartItems.slice(idx+1)]
      } 
      break;
    default:
      // 아무 조건도 만족하지 않는 경우
      return state;
  }
}

export default itemReducer;

🙋🏻‍♀️ 회고

React Hooks 과제 파일 구조랑 코드 분석을 꼼꼼히 했더니 이번 과제는 그때만큼 어렵지는 않았다. test케이스가 있어서 그런지 뭘 먼저 건드려야할지 알 것 같은 느낌이랄까..?ㅎㅎ 그래도 해결아되는 부분 코드를 2시간씩 고민하면서 해결한 희열을 잊지 못한 과제였다... 후.. 힘든만큼 보람이 있었던 과제였던 것 같다

0개의 댓글