Redux를 활용하여(정확히는 Redux Toolkit) 장바구니를 만드는 실습 예제
사용자 정보 state (userSlice)
공유 대상 state를 개별로 관리하기 위해 src 폴더에 하위 폴더 store 폴더를 생성하고 사용자 정보를 저장해 둘 userSlice.js를 추가한다.
src/store/userSlice.js (이 예제는 코딩애플의 실습내용과 다르다. redux-toolkit의 quick-start에 서술된 방식을 사용했다)
// createSlice 사용을 위한 import
import { createSlice } from '@reduxjs/toolkit'
// state의 최초 상태를 정의
const initialState = {
name: 'UserName',
age: 21
}
// store대상 state를 정의한다.
export const user = createSlice({
name: 'user',
initialState,
reducers: {
// state에 영향을 미칠 method를 정의한다.
setName(state, action) {
state.name = action.payload
},
increaseAge(state) {
state.age++
}
}
})
// reducers에 정의한 method를 export 해준다.
export const { setName, increaseAge } = user.actions
export default user.reducer
이전 글에서 작성한 store.js의 configureStore에 user state를 추가하여 store에 저장한다.
src/store.js
import { configureStore } from '@reduxjs/toolkit'
// userSlice.js를 import
import userReducer from './store/userSlice.js'
export default configureStore({
reducer: {
// user 추가
// userSlice에서 export default로 user.reducer를 지정했기때문에
// import 문장의 userReducer가 user.reducer를 지칭하게 된다.
// userReducer 대신 user.reducer를 사용해도 된다.
user : userReducer
}
})
저장한 state를 redux store에서 불러오고 method를 호출해보자.
장바구니 내용은 cart.js에 작성했으며, Table ui 디자인 내용은 설명하지 않는다.
src/pages/Cart.js
import { useSelector, useDispatch } from 'react-redux'
import { increaseAge } from '../store/userSlice.js'
function Cart() {
// store.js의 state는 useSelector() method로 불러올 수 있다.
let user = useSelector((state) => state.user)
// state의 reducers내 method는 dispatch 기능으로 호출해야 함으로 dispatch를 하나 선언해준다.
let dispatch = useDispatch()
return (
<div>
<h6>{ user.name }({ user.age })의 장바구니</h6>
{/* 버튼을 눌러 user.age를 증가시키도록 increaseAge method를 dispatch로 호출한다 */}
<button onClick={() => { dispatch(increaseAge) }>버튼</button>
</div>
)
}
이와 같이 작성하면 서버/cart 페이지에 접속하면 아래와 같이 보인다.
버튼을 클릭하면 UserName 옆의 숫자가 증가한다.
장바구니 항목 state (cartItemsSlice)
src/store/cartItems.js 파일을 추가한다.(cartItems는 코딩애플 강좌의 예제와 같이 구현했다. initialState를 별도로 뺄 방법을 모르겠음)
import { createSlice } from '@reduxjs/toolkit'
let cartItems = createSlice({
name: 'cartItems',
initialState: [
{ id: 0, name: 'White and Black', count: 2 },
{ id: 2, name: 'Grey yordan', count: 1 }
],
reducers: {
// reducer 내 method의 parameter
// state는 state 자체를 의미한다.
// action에는 action의 type, payload 등이 담기는데 method를 호출하면서 사용한 parameter는 action.payload에 담겨있다.
increaseCount(state, action) {
// array로 구성된 state의 특정 object에 대해서만 조작하고싶은 경우
// state.find(), state.findIndex() method를 통해 object 혹은 object의 index를 찾을 수 있다.
let targetItem = state.find(x => x.id === action.payload)
if (targetItem !== null) {
// state의 property를 변경하고 setState등의 동작없이 즉시 반영된다.
targetItem.count++
}
},
decreaseCount(state, action) {
let targetItem = state.find(x => x.id === action.payload)
if (targetItem !== null && targetItem.count > 0) {
targetItem.count--
}
},
// detail 페이지에서 주문하기를 눌렀을 때 장바구니에 해당 항목을 추가하는 method
addCart(state, action) {
let existCheck = state.find((x) => { return x.id === action.payload.id })
if (existCheck !== null && typeof existCheck !== 'undefined') {
// 이미 장바구니에 존재하는 항목이면 개수를 증가시킨다.
existCheck.count++
}
else {
// 장바구니에 없는 항목이면 state에 항목을 추가한다.
state.push(action.payload)
}
},
removeFromCart(state, action) {
let existCheck = state.find((x) => { return x.id === action.payload })
if (existCheck !== null && typeof existCheck !== 'undefined') {
let removeIdx = state.findIndex((x) => { return x.id === action.payload })
state.splice(removeIdx, 1)
}
}
}
export let { increaseCount, decreaseCount, addCart, removeFromCart } = cartItems.actions
export default cartItems
store.js에 cartItems state를 추가한다.
src/store.js
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './store/userSlice.js'
import cartItems from './store/cartItemsSlice.js'
export default configureStore({
reducer: {
user: userReducer,
cartItems: cartItems.reducer
}
})
detail page에서 주문하기 버튼을 눌러 장바구니에 항목을 추가하자 (addCart() 호출)
src/pages/Detail.js (redux 호출부만 적는다)
import { useDispatch } from 'react-redux'
import { addCart } from '../store/cartItemsSlice.js'
function DetailPage (props) {
...
// router로 전달받은 id
let { id } = useParams()
// props로 전달받은 shoes state에서 현재 페이지 대상 state 추출
let shoes = props.shoes.find((x) => { return x.id === id })
// addCart 호출을 위한 dispatch 선언
let dispatch = useDispatch()
return (
...
<button className='btn btn-danger' onClick={() => {
// cartItems의 구성과 동일하게 id, name, count 구성의 object를 payload로 보낸다.
dispatch(addCart({ id: shoes.id, name: shoes.content, count: 1 }))
}}>주문하기</button>
...
)
}
장바구니 페이지의 수량 변경 및 삭제 기능 구현
src/pages/Cart.js
import { Table } from 'react-bootstrap'
import { useSelector, useDispatch } from 'react-redux'
import { increaseCount, decreaseCount, removeFromCart } from '../store/cartItemsSlice.js'
function CartItemRows(props) {
// redux store의 cartItems 선택
let cartItems = useSelector((state) => { return state.cartItems })
let dispatch = userDispatch()
return (
<>
{
cartItems.map(
function (x, rowIdx) {
return (
...
<button className='btn btn-outline-primary btn-sm' onClick={() => {
// x는 cartItems array중 현재 순회중인 object임
dispatch(increaseCount(x.id))
}}>+</button>
<button className='btn btn-outline-primary btn-sm' onClick={() => {
dispatch(decreaseCount(x.id))
}}>-</button>
...
<button className='btn btn-outline-primary' onClick={() => {
dispatch(removeFromCart(x.id))
}}>삭제</button>
...
)
}
}
</>
)
}
Detail 페이지에서 주문하기를 눌렀는데 장바구니에 변화가 없는데?
url을 입력해서 페이지를 이동하는 경우 state가 초기화된다. 그리고 NavBar의 장바구니가 navigate()가 아니라 href로 이동하도록 처리되어 있는 경우 url을 입력해서 페이지를 이동하는 것과 같으므로 navigate() method를 반드시 사용하자.
이 부분 때문에 4시간을 뻘짓함.
'개발 > React 학습' 카테고리의 다른 글
memo, useMemo (0) | 2023.04.27 |
---|---|
Lazy Import (0) | 2023.04.27 |
Redux 설치 (0) | 2023.04.21 |
Context API (0) | 2023.04.21 |
Transition (0) | 2023.04.20 |