개발

리액트 5일차 : Redux

Domaya 2022. 12. 30. 22:37

기존에 리액트를 사용하면서

컴포넌트가 많아짐에 따라 state 관리가 힘들어진다는 걸 느낀 적이 있다.

여러 컴포넌트에서 같이 사용하거나,

부모의 부모 컴포넌트에서 사용되는 state를 물려 받아야 한다면

이를 props로 전달해주는 것은 무척 번거롭다...(예전에 투두메이트 클론코딩을 하다가 집어던진 이유도 이거였다ㅠ)

 

Redux는 전역적으로 state를 관리하게 도와준다.

 

https://kyun2da.dev/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/Redux-%EC%A0%95%EB%A6%AC/

 

Redux 정리

Redux란? 리덕스는 리액트에서 가장 많이 사용되는 상태 관리 라이브러리중 하나이다. 리덕스를 사용하면 컴포넌트의 상태 업데이트 관련 로직을 다른 파일로 분리시켜서 효율적으로 관리할 수

kyun2da.dev

  1. 액션(Action)
    {
       type: 'ADD_TODO',
       data: {
           id: 1,
           text: '리덕스 배우기'
       }
    }

    상태에 어떠한 변화가 필요하면 action이 발생한다. 이는 하나의 객체로 표현된다.
    액션객체는type필드를 반드시 가지고 있어야 한다. 이값을  액션의 이름이라고 생각하면 되고, 나머지 값들은 나중에 상태 업데이트를 할 때 참고해야 할 값이며 작성자 마음대로 넣을 수 있다

  2. 리듀서(reducer)
    const initialState = {
     counter: 1,
    }
    function reducer(state = initialState, action) {
     switch (action.type) {
       case INCREMENT:
         return {
           counter: state.counter + 1,
         }
       default:
         return state
     }
    }
    변화를 일으키는 함수. 액션을 만들어서 발생시키면 리듀서가 현재 상태와 전달받은 액션 객체를 파라미터로 받아온다. 그리고 두 값을 참고하여 새로운 상태를 만들어 반환해준다
  3. 스토어(Store)
    스토어안에는 현재 어플리케이션 상태와 리듀서가 들어가 있으며 그 외에도 몇 가지 중요한 내장 함수를 지닌다. 하나의 프로젝트는 하나의 스토어만 가질 수 있다.
  4. 디스패치(Dispatch)
    스토어의 내장 함수 중 하나인 디스패치는 액션 객체를 넘겨줘서 상태를 업데이트 하는 유일한 방법이다. 이 함수가 호출되면 스토어는 리듀서 함수를 실행시켜서 새로운 상태를 만들어 준다

 

실습

store.js

import { configureStore, createSlice, current  } from "@reduxjs/toolkit";
import data from "./data";
import { React, useEffect, useState } from 'react'

// { name : 'state Name',  initialState : 'state Value'}
let user = createSlice({     //useState() 역할임
    name : 'user',
    initialState : '도마야',     // user='도마야'
    reducers : {
        changeName(state){
            return 'DOMAYA'
        }
    }
})
export let {changeName} = user.actions; //state변경함수
let peple = createSlice({
    name : 'peple',
    initialState : 200    // peple = 200
})
let stock = createSlice({ //    [stock, setStock] = useState([7,13,20]);
    name : 'stock',
    initialState : [7,13,20],    // stock = [7,13,20]
    reducers : {
        changeStock(state, action){
            console.log(action)
            let copy = [...state]
            let arr =[];
            if(action.payload == 'up'){
                copy.map((item)=>{item++; arr.push(item)})
                return arr;
            }else{
                copy.map((item)=>{item--; arr.push(item)})
                return arr;
            }
        }
    }
})
export let {changeStock} = stock.actions;


let cart = createSlice({
    name : 'cart',
    initialState :
    [   // cart = [{},{}]
        { id : 0, title : "Black and White", count: 2},
        { id : 0, title : "Red Knit", count: 1}
    ],
    reducers : {
        changeCart(state, action){

            // console.log(state);
            console.log(action)
            let clothes = [...state];
            clothes.push(data[action.payload])
            return clothes;

        },
        changeCount(state, action){
            if(state[action.payload[0]].count > 0){
                state[action.payload[0]].count += action.payload[1];
            }
            
        }
    }
})

export let {changeCart, changeCount} = cart.actions;


export default configureStore({   // 상태변수 등록 하는 부분
    reducer: {
        user : user.reducer,
        peple : peple.reducer,
        stock : stock.reducer,  
        cart  : cart.reducer
    }
})

detail.js

이 컴포넌트에서는 장바구니 버튼을 누르면 장바구니 state가 업데이트되어야 한다.

import { React, useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom';
import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs'
import { useDispatch, useSelector } from "react-redux";
import { changeCart } from './../store';


function Detail(props) {


  let value = useSelector((state) => state)
  console.log(value);
  let dispatch = useDispatch(); //actions에 있는 걸 호출해주는

  let { id } = useParams(); //HOOK
  let findId = props.data.find((item) => item.id == id);
  const navigate = useNavigate();
  let [count, setCount] = useState(0);
  const [flag, setflag] = useState(true);
  let [clickTab, setClickTab] = useState(0);


  return (
    <>
  
      <div className='container'>
        <div className='row'>

          <div className='col-md-12 mt-4'>
            <img src={findId.img} className='img-fluid' alt='yb' />
            <h4>{findId.title}</h4>
            <p>{findId.content}</p>
            <p>{findId.price}원</p>

            <button className="btn-danger">주문하기</button>
            <button className='btn-warning' onClick={() => { navigate(-1) }} >뒤로가기</button>
            <button className='btn-primary' onClick={() => { navigate('/') }} >홈</button>
            <button className='btn-dark' onClick={() => { dispatch(changeCart(id));navigate('/cart') }}>장바구니</button>

          </div>
        </div>
      </div>
    </>
  )
}

export default Detail;

react-redux로부터 

import { useDispatch, useSelector } from "react-redux";

를 해주고,

store에 만들어둔 리듀서를 임포트해야 한다.

import { changeCart } from './../store';

그리고 장바구니 버튼을 클릭하면 상품이 담기도록, 해당 리듀서를 실행시켜야 한다.

그러기 위해 dispatch를 선언하고,

  let dispatch = useDispatch(); //actions에 있는 걸 호출해주는

버튼에는 onclick으로

 <button className='btn-dark' onClick={() => { dispatch(changeCart(id));navigate('/cart') }}>장바구니</button>

이렇게...

 

 

<button className="btn-sm btn-dark" onClick={()=>{dispatch(changeCount([index,1]))}}>+</button>&nbsp;
<button className="btn-sm btn-dark" onClick={()=>{dispatch(changeCount([index,-1]))}}>-</button>

Cart.js에 있는 이 두 버튼은

클릭하면 수량을 늘리거나 줄이는 역할을 한다.

 

장바구니

 

저 플러스 마이너스 버튼을 누를 때마다 store.js에 있는 리듀서가 호출된다.

let cart = createSlice({
    name : 'cart',
    initialState :
    [   // cart = [{},{}]
        { id : 0, title : "Black and White", count: 2},
        { id : 0, title : "Red Knit", count: 1}
    ],
    reducers : {
        changeCart(state, action){

            // console.log(state);
            console.log(action)
            let clothes = [...state];
            clothes.push(data[action.payload])
            return clothes;

        },
        changeCount(state, action){
            if(state[action.payload[0]].count > 0){
                state[action.payload[0]].count += action.payload[1];
            }
            
        }
    }
})

export let {changeCart, changeCount} = cart.actions;

저 changeCount가 해당 리듀서임

action.payload에는 index와 증감값이 들어간다.

배열 형태로 들어가기 때문에

action.payload[0]에는 인덱스가, action.payload[1]에는 +1 혹은 -1이 들어간다.