소스:
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)
댓글 남기기