105일차 - React (redux 실습)

Yohan·2024년 7월 29일
0

코딩기록

목록 보기
148/156
post-custom-banner

리덕스 스토어 생성

import { configureStore } from "@reduxjs/toolkit";
import uiReducer from './ui-slice';
import cartReducer from './cart-slice';

// 단일 리덕스 스토어 생성
const store = configureStore({
  // 상태관리하고싶은 reducer들 적음
  reducer: {
    ui: uiReducer,
    cart: cartReducer
  }
})

export default store;

cart-slice 생성 (리듀서 업데이트)

  • 초기 상태 정의 (initialState)
  • 슬라이스 정의 (createSlice)
  • 액션 내보내기 (cartActions)
    • 상태관리할 컴포넌트에서 dispatch를 사용하여 액션을 보낼 수 있음
  • 리듀서 내보내기 (default export)
    • 스토어를 설정할 때 이 리듀서를 사용
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  cartItems: [], // 장바구니에 담긴 상품 객체들
  totalQuantity: 0, // 장바구니 총 수량
};

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addCartItem(state, action) {
      // 추가될 상품 객체 
      const newCartItem = action.payload;
      // 이 상품이 기존 장바구니배열에 있는 상품인지 탐색
      const existingItem = state.cartItems.find(item => item.id === newCartItem.id);

      if (!existingItem) { // 처음 장바구니에 추가된 상품인 경우
        state.cartItems.push(newCartItem);
      } else {
        existingItem.quantity++;
        existingItem.total += newCartItem.price;
      }
      state.totalQuantity++;
    },
    removeCartItem(state, action) {
      const id = action.payload;
      const existingItem = state.cartItems.find(item => item.id === id);

      if (existingItem.quantity === 1) { // 수량이 1인 경우 배열에서 삭제
        // const index = state.cartItems.findIndex(item => item.id === id);
        // state.cartItems.splice(index, 1);

        state.cartItems = state.cartItems.filter(item => item.id !== id);

      } else { // 수량, 총액 갱신
        existingItem.quantity--;
        existingItem.total -= existingItem.price;
      }
      state.totalQuantity--;
    }
  }
});

export const cartActions = cartSlice.actions;
export default cartSlice.reducer;

카트 버튼 조건부 렌더링

ui-slice 생성

import { createSlice } from "@reduxjs/toolkit";

// 상태관리할 값들의 초기값
const initialState = {
  cartIsVisible: false,
};

const uiSlice = createSlice({
  name: 'ui',
  initialState, // key, value랑 이름이 같으면 한번만써도됨
  reducers: {
    // 상태변경 행위 (액션함수)
    toggle(state) {
      state.cartIsVisible = !state.cartIsVisible;
    },
  }
});

// 액션 함수들 내보내기
export const uiActions =  uiSlice.actions;
export default uiSlice.reducer;

CartButton.js

  • Cart 조건부 렌더링을 위해 toggle핸들러 CartButton에 걸어놓음
    • 액션은 dispatch로 걸기
import React from "react";
import styles from './CartButton.module.css';
import { uiActions } from "../../store/ui-slice";
import { useDispatch } from "react-redux";

const CartButton = () => {

  const dispatch = useDispatch();

  const toggleCartHandler = e => {
    dispatch(uiActions.toggle());
  };

  return (
    <button className={styles.button} onClick={toggleCartHandler}>
      <span>My Cart</span>
      <span className={styles.badge}>0</span>
    </button>
  );
};

export default CartButton;

App.js

  • ui-slice의 cartIsVisible 상태를 useSelector로 불러와서 Cart를 조건부 렌더링
import React from 'react';
import Layout from './redux-cart/components/Layout/Layout';
import Cart from './redux-cart/components/Cart/Cart';
import Products from './redux-cart/components/Shop/Products';
import { useSelector } from 'react-redux';

const App = () => {

  // useSelector(): store에 있는 상태값을 불러옴
  const isVisible = useSelector(state => state.ui.cartIsVisible);

  return (
    <Layout>
      {isVisible && <Cart />}
      <Products />
    </Layout>
  );
};

export default App;

카트에 상품 추가 상태관리

Cart.js

  • Redux store에서 장바구니 항목들을 가져와 화면에 표시
  • cartItems 배열을 useSelector 훅을 사용하여 가져오고, 각 항목을 CartItem 컴포넌트로 렌더링
import React from "react";
import Card from '../UI/Card';
import styles from './Cart.module.css';
import CartItem from './CartItem';
import { useSelector } from 'react-redux'

const Cart = () => {

    const cartItems = useSelector(state => state.cart.cartItems);

    console.log(cartItems)

  return (
    <Card className={styles.cart}>
      <h2>Your Shopping Cart</h2>
      <ul>
          {cartItems.map(item => <CartItem key={item.id} item={item}/>)}
      </ul>
    </Card>
  );
};

export default Cart;

CartItem.js

  • 장바구니에 담긴 개별 상품의 정보를 화면에 표시
  • "+" 버튼을 클릭하면 addCartHandler 함수가 실행되어 해당 상품의 수량을 증가
  • "-" 버튼을 클릭하면 removeCartHandler 함수가 실행되어 해당 상품의 수량을 감소
import React from "react";
import styles from './CartItem.module.css';
import { cartActions } from "../../store/cart-slice";
import { useDispatch } from "react-redux";

const CartItem = ({item}) => {

  const dispatch = useDispatch();

  const { title, quantity, total, price, id } = item;

  const addCartHandler = e => {
    dispatch(cartActions.addCartItem(item));
  };

  const removeCartHandler = e => {
    dispatch(cartActions.removeCartItem(id));
  };

  return (
    <li className={styles.item}>
      <header>
        <h3>{title}</h3>
        <div className={styles.price}>u
          {total}{' '}
          <span className={styles.itemprice}>({price}/item)</span>
        </div>
      </header>
      <div className={styles.details}>
        <div className={styles.quantity}>
          x <span>{quantity}</span>
        </div>
        <div className={styles.actions}>
          <button onClick={removeCartHandler}>-</button>
          <button onClick={addCartHandler}>+</button>
        </div>
      </div>
    </li>
  );
};

export default CartItem;

ProductItem.js

  • Add to Cart 버튼을 클릭하면 addCartHandler 함수가 실행되어 Redux store에 상품을 추가되고 화면에 표시
import React from "react";
import Card from '../UI/Card';
import styles from './ProductItem.module.css';
import { cartActions } from "../../store/cart-slice";
import { useDispatch } from "react-redux";

const ProductItem = ({description, id, price, title}) => {

  const dispatch = useDispatch();

  const addCartHandler = e => {
    dispatch(cartActions.addCartItem({
      id,
      title,
      quantity: 1,
      price,
      total: price 
    }));
  };

  return (
    <li className={styles.item}>
      <Card>
        <header>
          <h3>{title}</h3>
          <div className={styles.price}>{price}</div>
        </header>
        <p>{description}</p>
        <div className={styles.actions}>
          <button onClick={addCartHandler}>Add to Cart</button>
        </div>
      </Card>
    </li>
  );
};

export default ProductItem;

카트에 상품 제거 상태관리

CartButton.js

  • 토탈 개수 상태관리위해 totalQuantity 가져옴
import React from "react";
import styles from './CartButton.module.css';
import { uiActions } from "../../store/ui-slice";
import { useDispatch, useSelector } from 'react-redux';

const CartButton = () => {

  // 상태 불러옴
  const totalQuantity = useSelector(state => state.cart.totalQuantity);

  const dispatch = useDispatch();

  // uiActions는 dispatch 메서드안에서 작업!
  const toggleCartHandler = e => {
    dispatch(uiActions.toggle());
  };

  return (
    <button className={styles.button} onClick={toggleCartHandler}>
      <span>My Cart</span>
      <span className={styles.badge}>{totalQuantity}</span>
    </button>
  );
};

export default CartButton;

CartItem.js

  • removeHandler 추가
import React from "react";
import styles from './CartItem.module.css';
import { cartActions } from "../../store/cart-slice";
import { useDispatch } from "react-redux";

const CartItem = ({item}) => {

  const dispatch = useDispatch();

  const { title, quantity, total, price, id } = item;

  const addCartHandler = e => {
    dispatch(cartActions.addCartItem(item));
  };

  const removeCartHandler = e => {
    dispatch(cartActions.removeCartItem(id));
  };

  return (
    <li className={styles.item}>
      <header>
        <h3>{title}</h3>
        <div className={styles.price}>u
          {total}{' '}
          <span className={styles.itemprice}>({price}/item)</span>
        </div>
      </header>
      <div className={styles.details}>
        <div className={styles.quantity}>
          x <span>{quantity}</span>
        </div>
        <div className={styles.actions}>
          <button onClick={removeCartHandler}>-</button>
          <button onClick={addCartHandler}>+</button>
        </div>
      </div>
    </li>
  );
};

export default CartItem;

cart-slice.js

  • 제거 리듀서 업데이트
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  cartItems: [], // 장바구니에 담긴 상품 객체들
  totalQuantity: 0, // 장바구니 총 수량
};

const cartSlice = createSlice({
  name: 'cart',
  initialState,
  reducers: {
    addCartItem(state, action) {
      // 추가될 상품 객체 
      const newCartItem = action.payload;
      // 이 상품이 기존 장바구니배열에 있는 상품인지 탐색
      const existingItem = state.cartItems.find(item => item.id === newCartItem.id);

      if (!existingItem) { // 처음 장바구니에 추가된 상품인 경우
        state.cartItems.push(newCartItem);
      } else {
        existingItem.quantity++;
        existingItem.total += newCartItem.price;
      }
      state.totalQuantity++;
    },
    removeCartItem(state, action) {
      const id = action.payload;
      const existingItem = state.cartItems.find(item => item.id === id);

      if (existingItem.quantity === 1) { // 수량이 1인 경우 배열에서 삭제
        // const index = state.cartItems.findIndex(item => item.id === id);
        // state.cartItems.splice(index, 1);

        state.cartItems = state.cartItems.filter(item => item.id !== id);

      } else { // 수량, 총액 갱신
        existingItem.quantity--;
        existingItem.total -= existingItem.price;
      }
      state.totalQuantity--;
    }
  }
});

export const cartActions = cartSlice.actions;
export default cartSlice.reducer;
profile
백엔드 개발자
post-custom-banner

0개의 댓글