소스:
https://github.com/braverokmc79/macaronics-reduxcart20
https://macaronics-reduxcart20.netlify.app/
1.리덕스 툴킷 설치:
리덕스 툴킷을 설치하려면 다음 명령어를 사용합니다.
# NPM을 사용하는 경우 npm install @reduxjs/toolkit react-redux # Yarn을 사용하는 경우 yarn add @reduxjs/toolkit react-redux
2.리덕스 툴킷 적용
리덕스 툴킷을 적용하려면 configureStore를 사용하여 스토어를 생성합니다. 이때, configureStore의 인자로 리듀서를 포함하는 객체를 전달합니다.
store/index.js
import {configureStore } from '@reduxjs/toolkit' ; import uiSlice from './ui-slice'; import cartSlice from './cart-slice'; const store=configureStore({ reducer:{ ui:uiSlice.reducer, cart:cartSlice.reducer } }); export default store;
3. Slice 생성:
createSlice를 사용하여 Slice를 생성합니다.
createSlice는 객체를 인자로 받으며, 이 객체에는 name, initialState, reducers가 필요합니다.
1) store/cart-slice.js
import { createSlice } from "@reduxjs/toolkit"; const cartSlice = createSlice({ name: "cart", initialState: { items: [], totalQuantity: 0, }, reducers: { addItemToCart(state,action) { const newItem=action.payload; const existingItem=state.items.find(item=>item.id===newItem.id); // console.log("existingItem ", existingItem); state.totalQuantity++; if(!existingItem) { state.items.push({ id:newItem.id, price:newItem.price, quantity:1, totalPrice:newItem.price, name:newItem.title }); }else{ // console.log("기존 제품 수량 업데이트"); existingItem.quantity++; existingItem.totalPrice += newItem.price; } }, removeItemFromCart(state, action) { const id=action.payload; const existingItem=state.items.find(item=>item.id===id); state.totalQuantity--; if(existingItem && existingItem.quantity === 1) { state.items=state.items.filter(item=>item.id!==id); }else{ existingItem.quantity--; existingItem.totalPrice -= existingItem.price; } }, }, }); export const cartActions = cartSlice.actions; export default cartSlice;
2) store/ui-slice.js
import {createSlice} from '@reduxjs/toolkit'; const uiSlice=createSlice({ name:"ui", initialState:{ cartIsVisible:false}, reducers:{ toggle(state){ console.log("장바구니 클릭"); state.cartIsVisible =!state.cartIsVisible; }, } }); export const uiActions=uiSlice.actions; export default uiSlice;
//dispatch 로 데이터를 넘겨주기위해
export const uiActions=uiSlice.actions;
//index 에서 ui:uiSlice.store 로 사용
//state 값을 가져올때 state.ui 로 접근
export default uiSlice;
4.컴포넌트에서 사용:
컴포넌트에서는 useSelector와 useDispatch를 사용하여 리덕스 스토어의 상태를 조회하거나 액션을 디스패치할 수 있습니다
CartItem.js ( dispatch 사용 예)
import { useDispatch } from 'react-redux'; import classes from './CartItem.module.css'; import { cartActions } from '../../store/cart-slice'; const CartItem = (props) => { const dispatch=useDispatch(); const {id, title, quantity, total, price } = props.item; const removeItemHandler=()=>{ dispatch(cartActions.removeItemFromCart(id)); } const addItemHandler=()=>{ dispatch(cartActions.addItemToCart({id,title,quantity,price})); } return ( <li className={classes.item}> <header> <h3>{title}</h3> <div className={classes.price}> ${total.toFixed(2)}{' '} <span className={classes.itemprice}>(${price.toFixed(2)}/item)</span> </div> </header> <div className={classes.details}> <div className={classes.quantity}> x <span>{quantity}</span> </div> <div className={classes.actions}> <button onClick={removeItemHandler}>-</button> <button onClick={addItemHandler}>+</button> </div> </div> </li> ); }; export default CartItem;
CartButton.js ( useSelector 사용예 )
import { useDispatch, useSelector } from "react-redux"; import { uiActions } from "../../store/ui-slice"; import classes from "./CartButton.module.css"; const CartButton = (props) => { const dispatch = useDispatch(); const cartQuantity= useSelector(state=>state.cart.totalQuantity) const toggleCartHandler = (event) => { event.preventDefault(); dispatch(uiActions.toggle()); console.log("장바구니"); }; return ( <button className={classes.button} onClick={toggleCartHandler}> <span>나의 장바구니</span> <span className={classes.badge}>{cartQuantity}</span> </button> ); }; export default CartButton;
5. root index.js 에 다음과 같이 설정을 해줘야 한다.
import ReactDOM from 'react-dom/client'; import {Provider} from 'react-redux'; import './index.css'; import App from './App'; import store from './store'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<Provider store={store} ><App /></Provider>);
※ 리덕스 툴킷과+ useEffect + fetch 를 이용한 데이터 가져오기
App.js
import { Fragment, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Cart from './components/Cart/Cart'; import Layout from './components/Layout/Layout'; import Products from './components/Shop/Products'; import { uiActions } from './store/ui-slice'; import Notification from './components/UI/Notification'; let isInitial =true; function App() { const dispatch=useDispatch(); const showCart = useSelector(state => state.ui.cartIsVisible); // 장바구니 표시 여부 const cart = useSelector(state => state.cart); // 장바구니 데이터 const notification=useSelector(state => state.ui.notification); // useEffect 훅을 사용하여 cart 데이터가 변경될 때마다 실행 useEffect(() => { const sendCardData = async () => { dispatch(uiActions.showNotification({ status:'pending', title:'Sending...', message:'장바구니 데이터를 전송 중입니다!' })); const response = await fetch('https://react-http-6b4a6.firebaseio.com/cart.json', { method: 'PUT', body: JSON.stringify(cart), }) if (!response || !response.ok) {; throw new Error('장바구니 데이터를 전송하지 못했습니다.'); } dispatch(uiActions.showNotification({ status:'success', title:'Success!', message:'장바구니 데이터가 성공적으로 전송되었습니다!' })); }; if(isInitial){ isInitial=false; return; } sendCardData().catch((error) => { //console.error(" 에러 :", error); dispatch(uiActions.showNotification({ status:'error', title:'Error!', message:'장바구니 데이터 전송에 실패했습니다!' })); }); }, [cart, dispatch]); // useEffect 의존성 배열에 cart와 dispatch 추가 return ( <Fragment> {notification && <Notification status={notification.status} title={notification.title} message={notification.message} /> } <Layout> {/* 레이아웃 컴포넌트 */} {/* 장바구니 표시 여부에 따라 Cart 컴포넌트 조건부 렌더링 */} {showCart && <Cart />} {/* 제품 목록 컴포넌트 */} <Products /> </Layout> </Fragment> ); } export default App;
※ 리액트에서 Thunk 란 무엇인가?
리액트에서 Thunk는 주로 비동기 작업을 처리하는 데 사용되는 미들웨어입니다.
Redux Thunk를 사용하면 액션 객체가 아닌 함수를 디스패치할 수 있게 해줍니다.
즉, 디스패치(dispatch)할 때 객체가 아닌 함수를 디스패치할 수 있게 되는 것입니다.
이렇게 함수를 디스패치하면, 해당 함수에서 디스패치와 getState를 파라미터로 받아와서 원하는 작업을 수행할 수 있습니다.
이렇게 중간에 원하는 작업을 함수를 통해 넣을 수 있고, 그것이 중간에 실행되는 것입니다.
이 함수를 Thunk 함수라고 부릅니다.
Redux Thunk는 리덕스에서 비동기 작업을 처리할 때 가장 많이 사용하는 미들웨어로, 이 미들웨어를 사용하면 액션 객체가 아닌 함수를 디스패치할 수 있습니다. 함수를 디스패치할 때는, 해당 함수에서 디스패치와 getState를 파라미터로 받아와야 합니다.
이 함수를 만들어주는 함수를 Thunk라고 부릅니다.
즉, Thunk는 리덕스에서 비동기 작업을 처리하기 위해 사용되는 미들웨어로, 액션 생성자가 액션 객체가 아닌 함수를 반환하게 해주는 역할을 합니다.
이 함수는 원하는 작업을 수행한 후에 액션 객체를 디스패치하게 됩니다.
https://github.dev/braverokmc79/macaronics-reduxcart20
1. 다음은 리덕스 툴킷이다.
리덕스 툴킷 만으로는 비동기식 데이터를 처리할수 없기 때문에 Thunk 사용한다고 생각하면 된다.
cart-slice.js
import { createSlice } from "@reduxjs/toolkit"; const cartSlice = createSlice({ name: "cart", initialState: { items: [], totalQuantity: 0, }, reducers: { replaceCart(state, action) { state.totalQuantity = action.payload.totalQuantity; state.items = action.payload.items; }, addItemToCart(state,action) { const newItem=action.payload; const existingItem=state.items.find(item=>item.id===newItem.id); state.totalQuantity++; if(!existingItem) { state.items.push({ id:newItem.id, price:newItem.price, quantity:1, totalPrice:newItem.price, name:newItem.title }); }else{ existingItem.quantity++; existingItem.totalPrice += newItem.price; } }, removeItemFromCart(state, action) { const id=action.payload; const existingItem=state.items.find(item=>item.id===id); state.totalQuantity--; if(existingItem && existingItem.quantity === 1) { state.items=state.items.filter(item=>item.id!==id); }else{ existingItem.quantity--; existingItem.totalPrice -= existingItem.price; } }, }, }); export const cartActions = cartSlice.actions; export default cartSlice;
2.다음과 같이 비동기식 createAsyncThunk 만든다.
cart-action.js
import { createAsyncThunk } from "@reduxjs/toolkit"; import { uiActions } from "./ui-slice"; import { cartActions } from "./cart-slice"; export const sendCartData = createAsyncThunk( "cart/sendCartData", async (cartData, { dispatch }) => { dispatch( uiActions.showNotification({ status: "pending", title: "Sending...", message: "장바구니 데이터를 전송 중입니다!", }) ); try { const response = await fetch( "https://react-http-6b4a6.firebaseio.com/cart.json", { method: "PUT", body: JSON.stringify(cartData), } ); if (!response || !response.ok) { throw new Error("장바구니 데이터를 전송하지 못했습니다."); } dispatch( uiActions.showNotification({ status: "success", title: "Success!", message: "장바구니 데이터가 성공적으로 전송되었습니다!", }) ); } catch (error) { dispatch( uiActions.showNotification({ status: "error", title: "Error!", message: "장바구니 데이터 전송에 실패했습니다!", }) ); } } ); //장바구니 가져오기 export const fetchCartData = createAsyncThunk( "cart/fetchCartData", async (cartData, { dispatch }) => { try { const response = await fetch( "https://jsonplaceholder.typicode.com/todos", { method: "GET", } ); if (!response || !response.ok) { throw new Error("장바구니 가져오기 실패."); } const data = await response.json(); const cartDataItem= data.map((item, index)=>{ const price=getRandomPrice(300, 500)*100; return ( { id:item.id, price:price, quantity:1, totalPrice:price, name:item.title, img:`https://source.unsplash.com/random/320*240?random=${index}` } ) }); const cart={ items: cartDataItem, totalQuantity:cartDataItem.length, } dispatch(cartActions.replaceCart(cart)); console.log("장바구니 가져오기 성공"); } catch (error) { console.error("에러(장바구니 가져오기 실패) : " , error ); } } ); function getRandomPrice(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } export const getCart = createAsyncThunk( "cart/getCart", async (cartData, { dispatch }) => { // 여기에 비동기 작업을 수행하세요. } );
3. 사용 예
cart-action.js 에서
액션 생성자가 액션 객체가 아닌 함수를 반환하여 함수 자체를 사용하고 있다.
dispatch(sendCartData(cart));
App.js
import { Fragment, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Cart from './components/Cart/Cart'; import Layout from './components/Layout/Layout'; import Products from './components/Shop/Products'; import Notification from './components/UI/Notification'; import { fetchCartData, sendCartData } from './store/cart-action'; let isInitial =true; function App() { const dispatch=useDispatch(); const showCart = useSelector(state => state.ui.cartIsVisible); // 장바구니 표시 여부 const cart = useSelector(state => state.cart); // 장바구니 데이터 const notification=useSelector(state => state.ui.notification); useEffect(()=>{ //1.장바구니 목록 가져오기 dispatch(fetchCartData()); }, [dispatch]); //2.장바구니 넣기 useEffect(() => { if(isInitial){ isInitial=false; return; } dispatch(sendCartData(cart)); }, [cart, dispatch]); // useEffect 의존성 배열에 cart와 dispatch 추가 return ( <Fragment> {notification && <Notification status={notification.status} title={notification.title} message={notification.message} /> } <Layout> {/* 레이아웃 컴포넌트 */} {/* 장바구니 표시 여부에 따라 Cart 컴포넌트 조건부 렌더링 */} {showCart && <Cart />} {/* 제품 목록 컴포넌트 */} <Products /> </Layout> </Fragment> ); } export default App;
댓글 ( 0)
댓글 남기기