Redux는 Context API 를 대신해서 props 없이도 state를 공유할 수 있는 외부 라이브러리이다
store.js 에 state를 보관해서 필요한 곳에 꺼내쓰는 것이 특징이다
일단 언제나 그랬듯 /cart 페이지를 Route를 통해 만들어준다
<Route path='/cart' element={<Cart></Cart>}></Route>
⚠ <Route= path="*" > 위에 작성을 하지 않으면 에러 발생
Cart 컴포넌트는 Cart.js에서 만들었다
import { Table } from "react-bootstrap";
export function Cart(){
return(
<>
<Table>
<thead>
<tr style={{fontSize:'12px'}}>
<th>번호</th>
<th>상품명</th>
<th>수량</th>
<th>변경</th>
</tr>
</thead>
<tbody>
{state.cart.map((item,i)=>
<tr className="align-middle">
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.count}</td>
<td><span onClick={()=>{}}>➕</span></td>
</tr>
)}
</tbody>
</Table>
</>
)
}
이제 Redux 사용을 위한 세팅을 해보겠다
npm install @reduxjs/toolkit react-redux
redux toolkit은 redux의 개선 버전이다
package.json에서 react, react-dom 이 18.1 버전 이상일 때 사용 가능하다
state들을 보관하는 파일
redux 세팅을 위해 store.js 파일을 만들어서 아래 코드를 작성한다
import { configureStore } from '@reduxjs/toolkit'
export default configureStore({
reducer: { }
})
import { Provider } from "react-redux";
import store from './store.js'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
index.js에서 <Provider>
로 <BrowserRouter>
를 감싸준다
그리고 <Provider>
에 store = {import 해온 store.js}
를 입력하면 세팅 끝이다 !
그럼 이제 <App>
과 그 모든 자식컴포넌트들은 store.js에 있던 state를 맘대로 꺼내쓸 수 있다
기본 세팅만 해둔 store 파일에 이번엔 state를 추가해볼 것이다
CreateSlice( ) 로 state 만든 후
나는 user name을 위한 user state 와 장바구니 데이터를 위한 cart state 두개를 만들었다
name 에는 state 명, initailState 에는 초깃값 (useState 안에 작성하는 값)
let user=createSlice({
name:'user',
initialState:{name:'jiwon',age:25},
})
let cart= createSlice({
name:'cart',
initialState: [
{id : 0, name : 'White and Black', count : 2},
{id : 2, name : 'Grey Yordan', count : 1}
],
configureStore( ) 안에 등록
이 안에 등록해야 모든 컴포넌트에서 state를 자유롭게 사용 가능해진다
export default configureStore({
reducer: {
cart:cart.reducer,
user:user.reducer
}
})
이제 해당 state를 사용할 Cart.js에서 사용을 위한 세팅을 해야한다
import { useSelector } from "react-redux"
export function Cart(){
let state= useSelector((state)=> state)
useSelector
을 통해 내가 store.js에 만들어둔 state를 받아올 수 있다
난 이렇게 사용하였다 🔽
return(
<>
<h6 className="my-4"><strong>{state.user.name}</strong> 의 장바구니</h6>
<Table>
<thead>
<tr style={{fontSize:'12px'}}>
<th>번호</th>
<th>상품명</th>
<th>수량</th>
<th>변경</th>
<th>삭제</th>
</tr>
</thead>
<tbody>
{state.cart.map((item,i)=>
<tr className="align-middle">
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.count}</td>
<td><span onClick={()=>{}}>➕</span></td>
...
장바구니 페이지의 user name을 설정해주었고
배열 형태의 cart state를 map 함수를 통하여 데이터 바인딩 해주었다
상품 수량 변경 버튼을 클릭할 때마다 count를 +1 씩 증가시켜보겠다
기존 state에서는 setter 함수가 있었지만 redux에서는 reducers에 함수를 추가해주어야 한다
let cart= createSlice({
name:'cart',
initialState: [
{id : 0, name : 'White and Black', count : 2},
{id : 2, name : 'Grey Yordan', count : 1}
],
//state 변경 함수 만드는 곳
reducers:{
changeCount(state,action){
//
export default configureStore({
...
reducers: state 변경 함수 만드는 곳
- 함수의 첫째 파라미터 : 기존 state
- 함수의 둘째 파라미터 : 사용하는 곳에서 받아오는 값
✔ action.type 하면 state변경함수 이름이 나오고
✔ action.payload 하면 파라미터가 나온다
configureStore 바로 위에 작성해주면 된다
export let {changeCount} = cart.actions
➡ slice명.actions
라고 적으면 변경함수가 전부 그 자리에 출력된다
useDispatch : store.js에 요청을 보내주는 함수
import { useDispatch, useSelector } from "react-redux";
import { changeName } from "./../store.js"
export function Cart(){
let state= useSelector((state)=>{return state})
let dispatch=useDispatch()
store.js에서 원하는 변경함수와 useDispatch를 import 해온 후, useDispatch의 결과를 변수로 받는다
<td>
<span
onClick={()=>{dispatch(changeCount(item.id))}}>➕</span>
</td>
그리고 dispatch( state변경함수() )
이렇게 감싸서 사용하면 된다
해당 함수에 값을 보내면 store.js의 reducers 에서 action.payload
로 받아오게 된다
이렇게 store 에서 state, setter 함수를 전부 보관하면 버그가 발생했 때 원인을 찾기 쉬워진다
cart 의 reducers에 changeCount
함수 구현
changeCount(state,action){
const index=state.findIndex((i)=>i.id===action.payload) //[0,1,...]
state[index].count+=1
},
"0번째 버튼을 누르면 state의 0번째 상품을 +1 해주세요~"
"1번째 버튼을 누르면 state의 1번째 상품을 +1 해주세요~"
위 기능을 구현하기 위해,
findIndex()
메소드를 사용하여 cart 배열의 인덱스를 뽑아서 클릭 이벤트가 발생한 값을 index 에 저장한다
그리고 해당 index의 count 를 증가시킨다
<button className="btn btn-danger" onClick={()=>{
dispatch(changeItem({id:data[id].id, name:data[id].title , count:1}))}}>주문하기</button>
클릭이벤트가 발생하면 cart slice 형태와 동일하게 id, name, count 값이 있는 객체 형태로써 인자를 받아온다
cart 의 reducers에 changeItem
함수 구현
changeItem(state,action){
if(state.some(item=>item.id===action.payload.id)){
const index=state.findIndex((i)=>i.id===action.payload.id) //[0,1,...]
state[index].count+=1
}else {
state.push(action.payload)
}
},
클릭한 상품이 이미 장바구니에 존재하냐/ 존재하지 않느냐를 나누어서 구현하였다
존재한다면 상품의 id와 클릭 id가 일치하는 상품의 count 값만 증가,
존재하지 않는다면 state에 해당 객체 push한다
<td><span onClick={()=>{ dispatch(deleteItem(item))}}>🗑</span></td>
cart 의 reducers에 deleteItem
함수 구현
deleteItem(state,action){
const index=state.findIndex((i)=>i.id===action.payload.id)
state.splice(index,1)
}
export let {changeCount,changeItem,deleteItem} = cart.actions
삭제 버튼 클릭 시 해당 item 객체를 받아와서, cart 데이터와 일치하는 것을 삭제한다