[React] 18. state 공유 - Redux로 장바구니 구현

지렁·2023년 11월 18일
0
post-custom-banner

Redux

Redux는 Context API 를 대신해서 props 없이도 state를 공유할 수 있는 외부 라이브러리이다

store.js 에 state를 보관해서 필요한 곳에 꺼내쓰는 것이 특징이다

🎈 이를 통해 장바구니 페이지인 /cart를 구현해보겠다 !



① STEP 1

🖤 페이지 먼저 구현

일단 언제나 그랬듯 /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 사용을 위한 세팅을 해보겠다


🖤 세팅

◾ redux 설치

npm install @reduxjs/toolkit react-redux

redux toolkit은 redux의 개선 버전이다

package.json에서 react, react-dom 이 18.1 버전 이상일 때 사용 가능하다

◾ store.js

state들을 보관하는 파일

redux 세팅을 위해 store.js 파일을 만들어서 아래 코드를 작성한다

import { configureStore } from '@reduxjs/toolkit'

export default configureStore({
  reducer: { }
}) 

◾ index.js

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를 맘대로 꺼내쓸 수 있다


② STEP 2

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 가져다 쓰기

이제 해당 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 함수를 통하여 데이터 바인딩 해주었다

③ STEP 3

🖤 store의 state 변경하기

상품 수량 변경 버튼을 클릭할 때마다 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 하면 파라미터가 나온다

◾ 다른곳에서 사용하기 위해 함수 export 하기

configureStore 바로 위에 작성해주면 된다

export let {changeCount} = cart.actions

slice명.actions 라고 적으면 변경함수가 전부 그 자리에 출력된다


◾ import 해서 사용하기 + dispatch()

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 함수를 전부 보관하면 버그가 발생했 때 원인을 찾기 쉬워진다


💪+ 버튼을 누르면 해당 상품의 수량부분이 +1 되는 기능 구현

store.js

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 를 증가시킨다


💪 상세페이지 주문하기 버튼을 누르면 새로운 상품이 state에 추가되는 기능 구현

Detail.js

<button className="btn btn-danger" onClick={()=>{
                dispatch(changeItem({id:data[id].id, name:data[id].title , count:1}))}}>주문하기</button>

클릭이벤트가 발생하면 cart slice 형태와 동일하게 id, name, count 값이 있는 객체 형태로써 인자를 받아온다

store.js

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한다


💪 삭제 버튼 구현

Cart.js

 <td><span onClick={()=>{ dispatch(deleteItem(item))}}>🗑</span></td>

store.js

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 데이터와 일치하는 것을 삭제한다

profile
공부 기록 공간 🎈💻
post-custom-banner

0개의 댓글