Redux Toolkit은 Redux의 사용을 더욱 간편하고 효율적으로 만들어주는 공식 툴셋입니다. 기존 Redux의 복잡한 설정과 반복적인 코드를 줄여주며, 쉽게 전역 상태 관리를 할 수 있도록 도와줍니다.
이번 글에서는 Redux Toolkit의 개념과 주요 기능, 그리고 사용법을 알아보겠습니다.
• Redux Toolkit이란 무엇인가요 ?
Redux Toolkit은 Redux의 공식 툴킷으로, 전역 상태 관리를 더욱 쉽게 하고 보일러플레이트 코드를 줄여주는 라이브러리입니다. Redux의 기본 개념은 그대로 유지하면서도 사용성을 크게 개선하여, 개발자가 효율적으로 상태 관리를 할 수 있도록 도와줍니다.
Redux Toolkit의 주요 기능:
- 간편한 설정: configureStore를 통해 미들웨어와 리듀서를 손쉽게 설정할 수 있습니다.
- 보일러플레이트 감소: createSlice와 createAsyncThunk 등을 사용하여 복잡한 액션과 리듀서를 자동으로 생성할 수 있습니다.
- 내장된 DevTools: Redux DevTools와 통합되어 상태 관리의 디버깅과 추적이 용이합니다.
- 미들웨어 자동 설정: Thunk와 같은 기본적인 미들웨어가 자동으로 포함되어 있어, 비동기 작업을 손쉽게 처리할 수 있습니다.
• Redux와 차이점은 무엇인가요?
Redux | Redux Toolkit |
설정이 복잡하고 보일러플레이트 코드가 많음 | 간단한 설정과 보일러플레이트 코드가 적음 |
리듀서, 액션 생성자 등을 수동으로 작성해야 함 | createSlice, createAsncThunk 로 자동생성 |
미들웨어 설정이 번거로움 | 기본적으로 Thunk 미들웨어가 포함됨 |
오류 처리가 복잡할 수 있음 | 내장된 툴로 쉽게 디버깅하고 상태를 관리할 수 있음 |
• createSlice (), configureStore()의 역할
createSlice()는 리듀서와 액션을 한 번에 생성할 수 있는 유틸리티 함수입니다. 상태, 리듀서, 액션 타입을 한 곳에서 관리할 수 있어 코드가 간결해지고 유지보수가 쉬워집니다.
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
}
}
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
configureStore()는 Redux 스토어를 설정할 때 사용하는 함수로, 미들웨어와 리듀서를 자동으로 결합하고 DevTools와 통합하는 기능을 제공합니다. 기본적으로 Redux Thunk 미들웨어가 포함되어 있어 비동기 작업 처리가 간편합니다.
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
const store = configureStore({
reducer: {
counter: counterReducer,
}
});
export default store;
• Redux Toolkit에서 Thunk 사용법 → createAsncThunk() 란?
createAsyncThunk()는 Redux Toolkit에서 비동기 작업을 처리하기 위해 사용하는 유틸리티 함수입니다.
API 호출 같은 비동기 작업을 간편하게 처리할 수 있으며, 요청의 진행 상태(로딩, 성공, 실패)를 자동으로 관리합니다.
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 비동기 작업을 위한 Thunk 생성
export const fetchUserData = createAsyncThunk(
'user/fetchUserData',
async (userId, thunkAPI) => {
const response = await fetch(`https://api.example.com/user/${userId}`);
return response.json();
}
);
const userSlice = createSlice({
name: 'user',
initialState: { user: null, status: 'idle', error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUserData.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchUserData.fulfilled, (state, action) => {
state.status = 'succeeded';
state.user = action.payload;
})
.addCase(fetchUserData.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
}
});
export default userSlice.reducer;
- createAsyncThunk()는 비동기 작업이 진행될 때 각각의 상태(pending, fulfilled, rejected)를 자동으로 처리합니다.
- extraReducers를 사용하여 비동기 작업의 상태에 따라 다른 동작을 수행할 수 있습니다.
과제 코드:
redux폴더의 redux.js코드:
//import { legacy_createStore, combineReducers } from "redux"
import data from "../assets/data"
import { configureStore, createSlice } from "@reduxjs/toolkit"
export const menuSlice = createSlice({
name: 'menu',
initialState: data.menu,
reducers: {
}
})
export const cartSlice = createSlice({
name: 'cart',
initialState: [],
reducers: {
addToCart(state, action) { return [...state, action.payload] },
removeFromCart(state, action) { return state.filter((el) => action.payload.id !== el.id) },
}
})
export const store = configureStore({
reducer: {
menu: menuSlice.reducer,
cart: cartSlice.reducer
}
})
// export const addToCart = (options, quantity, id) => {
// return {
// type: 'addToCart',
// payload: { options, quantity, id }
// }
// }
// export const removeFromCart = (id) => {
// return {
// type: `removeFromCart`,
// payload: { id }
// }
// }
// export const cartReducer = (state = [], action) => {
// switch (action.type) {
// case 'addToCart': return [...state, action.payload]
// // action.payload가 { id } 형태이므로, action.payload.id로 접근
// case 'removeFromCart': return state.filter((el) => action.payload.id !== el.id)
// default: return state
// }
// }
// export const menuReducer = (state = data.menu, action) => {
// return state
// }
//const rootReducer = combineReducers({ cartReducer, menuReducer })
// export const store = legacy_createStore(rootReducer)
main.jsx 코드
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "./redux/redux.js"
ReactDOM.createRoot(document.getElementById("root")).render(
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
);
Cart.jsx
import { useDispatch, useSelector } from "react-redux";
import data from "../assets/data";
//import { removeFromCart } from "../redux/redux";
import { cartSlice } from '../redux/redux'
function Cart() {
// const menu = useSelector(state => state.menuReducer)
// const cart = useSelector(state => state.cartReducer)
const menu = useSelector(state => state.menu)
const cart = useSelector(state => state.cart)
if (!menu)
return (
<div style={{ textAlign: "center", margin: "80px" }}>
{" "}
메뉴 정보가 없어요!
</div>
);
const allMenus = [...menu.커피, ...menu.논커피];
return (
<>
<h2>장바구니</h2>
<ul className="cart">
{cart?.length ? (
cart.map((el) => (
<CartItem
key={el.id}
item={allMenus.find((menu) => menu.id === el.id)}
options={el.options}
quantity={el.quantity}
/>
))
) : (
<div className="no-item">장바구니에 담긴 상품이 없어요!</div>
)}
</ul>
</>
);
}
function CartItem({ item, options, quantity }) {
const dispatch = useDispatch()
return (
<li className="cart-item">
<div className="cart-item-info">
<img height={100} src={item.img} />
<div>{item.name}</div>
</div>
<div className="cart-item-option">
{Object.keys(options).map((el) => (
<div key={el.id}>
{el} : {data.options[el][options[el]]}
</div>
))}
<div>개수 : {quantity}</div>
</div>
<button
className="cart-item-delete"
onClick={() => {
dispatch(cartSlice.actions.removeFromCart({ id: item.id }))
// dispatch(removeFromCart(item.id))
//setCart(cart.filter((el) => item.id !== el.id));
}}
>
삭제
</button>
</li>
);
}
export default Cart;
Menu.jsx
import { useState } from "react";
import Item from "./Item";
import OrderModal from "./OrderModal";
import { useSelector } from "react-redux";
function Menu() {
const [modalOn, setModalOn] = useState(false);
const [modalMenu, setModalMenu] = useState(null);
// const menu = useSelector(state => state.menuReducer)
const menu = useSelector(state => state.menu)
if (!menu)
return (
<div style={{ textAlign: "center", margin: "80px" }}>
{" "}
메뉴 정보가 없어요!
</div>
);
const categorys = Object.keys(menu);
return (
<>
{categorys.map((category) => {
return (
<section key={category}>
<h2>{category}</h2>
<ul className="menu">
{menu[category].map((item) => (
<Item
key={item.name}
item={item}
clickHandler={() => {
setModalMenu(item);
setModalOn(true);
}}
/>
))}
</ul>
</section>
);
})}
{modalOn ? (
<OrderModal
modalMenu={modalMenu}
setModalOn={setModalOn}
/>
) : null}
</>
);
}
export default Menu;
OrderModal.jsx
import { useState } from 'react'
import data from '../assets/data'
import { useDispatch /*,useSelector*/ } from 'react-redux'
import { /*addToCart,*/ cartSlice } from '../redux/redux'
function OrderModal({ modalMenu, setModalOn }) {
const [options, setOptions] = useState({ '온도': 0, '진하기': 0, '사이즈': 0 })
const [quantity, setQuantity] = useState(1)
// const cart = useSelector(state => state.cartReducer)
const dispatch = useDispatch()
const itemOptions = data.options
console.log(options)
return (
<>
{modalMenu ? (
<section className="modal-backdrop" onClick={() => setModalOn(false)}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<div className='modal-item'>
<img src={modalMenu.img} />
<div>
<h3>{modalMenu.name}</h3>
<div>{modalMenu.description}</div>
</div>
</div>
<ul className="options">
{Object.keys(itemOptions).map(el => <Option
key={el}
options={options}
setOptions={setOptions}
name={el}
itemOptions={itemOptions[el]}
/>)}
</ul>
<div className="submit">
<div>
<label htmlFor="count" >개수</label>
<input id="count" type="number" value={quantity} min='1' onChange={(event) => setQuantity(Number(event.target.value))} />
</div>
<button onClick={() => {
dispatch(cartSlice.actions.addToCart({ options, quantity, id: modalMenu.id })) // 객체로 만들어서 키 이름이 명확해야한다 id키
//dispatch(addToCart(options, quantity, modalMenu.id))
//setCart([...cart, { options, quantity, id: modalMenu.id }])
setModalOn(false)
}}>장바구니 넣기</button>
</div>
</div>
</section>
) : null}
</>
)
}
function Option({ name, options, setOptions, itemOptions }) {
return (
<li className='option'>
{name}
<ul>
{itemOptions.map((option, idx) => (
<li key={option}>
<input type='radio' name={name} checked={options[name] === idx} onChange={() => setOptions({ ...options, [name]: idx })} />
{option}
</li>
))}
</ul>
</li>
)
}
export default OrderModal
https://github.com/CHOI-JUNWON99/oz-cafe-redux-toolkit/tree/main/src
'자기계발' 카테고리의 다른 글
React Developer Tools를 활용한 React 애플리케이션 성능 최적화 (0) | 2024.09.27 |
---|---|
리액트 성능 최적화의 기초: useMemo, useCallback, memo (2) | 2024.09.27 |
Redux로 전역상태 관리하기 (0) | 2024.09.26 |
Context API (0) | 2024.09.26 |
React ESLint로 편리하게 코딩하기 (0) | 2024.09.26 |