[React] Redux 리액트 상태 라이브러리

J.A.Y·2024년 3월 22일
0

javaScript

목록 보기
20/21
post-thumbnail

리액트에서 값을 저장하는 'State'라는 개념이 있지요. State에는 3가지 종류가 있다고 합니다.

  1. Local State
  2. Cross-Compoenet State
  3. App-Wide State

이전에 포스터에서 state와 useSate에 대해 간단히 정리했었는데, 이 둘은 local state에 해당합니다. Local State는 컴포넌트별로 소유하고 있는 것으로, 소유하고 있는 컴포넌트 내부에서만 관리되고 사용이 가능합니다.

반면, cross-component state와 app-wide state는 두 개 이상의 컴포넌트끼리 공유할 수 있습니다. Cross-component state는 두 개 이상의 컴포넌트에서 props를 통해 상태를 공유할 수 있고, app-wide state는 리액트 애플리케이션 전체 영역에서 사용가능한 상태입니다.

Redux는 바로 이 두 state 관리를 위한 라이브러리입니다.


Redux를 사용하는 이유

useState로 저장한 'name'이라는 state가 있다고 가정해보겠습니다. 만약 name을 여러 컴포넌트에서 사용하고 또 변경해주고자 한다고 했을 때, 어떤 상황이 벌어질까요?

한 컴포넌트에서 다른 컴포넌트로 전달하고, 또 다른 컴포넌트로 전달하고, 또 전달하고... 이러한 상황이 발생하겠지요. 여기에 'age'와 'address', 'email', 'hoby', 'description' 등 새로운 state를 여러 개 더 만들어서 사용한다면 어떠할까요? 심지어 이 모든 state들을 모든 컴포넌트에서 필요로 하는 것이 아니라, 재각기 다른 곳에서 필요하다고 한다면? 부모와 자식 관계가 굉장히 복잡해지겠지요.

그러나 한 곳에서 state들을 모두 저장하고, 부모 컴포넌트에서 자식 컴포넌트로 일일이 state를 넘겨줄 필요없이 각 컴포넌트에서 필요할 때마다 필요한 state를 가져와 사용할 수 있다면 얼마나 편리할까요?

하지만, Redux는 가능합니다!

Redux는 'store'이라는 state들을 저장할 수 있는 공간을 제공합니다. 먼저 'reducer'에 state를 정의하고, 이를 'store'에 저장하는 것이지요. 그리고 이 모든 state들을 'combine'시켜주는 함수를 이용해 결합시키면, 부모와 자식 사이의 props관계와 상관없이 필요한 state를 'useSelector'라는 함수로 가져올 수 있습니다. 그리고 이렇게 가져온 state를 'useDispatch'로 변경시켜줄 수 있고요.


Redux 용어 4가지

Redux를 사용하려면 4가지 용어에 대한 이해가 있어야 합니다. 앞서도 언급했지만, 용어에는 store, action, dispatch, select가 있습니다.

- Store:

Store는 상태를 관리할 수 있는 공간적 개념입니다. Store는 한 프로젝트에 한 개만 존재할 수 있습니다.

Store안에는 현재 어플리케이션의 상태와 reducer이 포함되어 있습니다. Reducer을 활용해서 어플리케이션의 상태를 바꿀 수 있습니다. (Store에 저장되어 있는 데이터를 컴포넌트에서 직접 조작하지 않습니다.)

- Action:

Action은 컴포넌트에서 store로 전달되는 데이터로, 상태 변화가 이루어지는 시점에 발생합니다. 여기서 데이터는 객체 형태로 전달됩니다.

- Dispatch:

Dispatch는 action을 발생시킵니다. Action 객체를 파라미터로 넣어 호출합니다.

- Reducer:

Reducer는 action의 type에 따라 변화를 일으키는 함수입니다. Reducer는 두 개의 매개 변수를 전달받는데, 첫 번째 변수는 state값이고, 두 번째는 action값을 받습니다. 다만, reducer로 http 요청을 하거나 데이터 저장 등은 할 수 없습니다.

[1] Store 생성 및 reducer 저장하기

0. redux & toolkit npm 설치

npm install redux
npm install redux react-redux @reduxjs/toolkit

1. store 생성

index.js에서 redner 함수가 실행되기 전 구간에 store을 생성해주는 코드를 작성합니다.

import {configureStore} from '@reduxjs/toolkit'
// import {createStore} from 'redux';
// 옛날 방식
// const initialState = {
    //변수: 값,
//}

const store = configureStore()

2. 'Count'에 관한 reducer을 정의해줍니다.

  • 새로운 폴더 'store'을 생성하고, 그 안에 counterReducer.js파일을 만듭니다.
  • reducer? 현재 상태 변화를 일으키는 함수
//store 초기값 정의
const initialState = {
    number: 50,
}

const counterReducer = (state = initialState, action) => {
    switch(action.type) {
        case 'PLUS':
            return {number: state.number +1};
        case 'MINUS':
            return {number: state.number -1};
        default:
            return state;
    }
}

//index에서 불러오기 위해 내보내줌
export default counterReducer;

3. store > index.js 에서 reducer combine하기

리듀서 여러 개를 하나로 합쳐줄 때 combineReducers를 사용합니다. 비록 지금 만들어져 있는 reducer는 하나지만, 이후 추가로 생성되는 reducer는 아래에 추가해주면 됩니다.

import { combineReducers } from "redux";
import counterReducer from "./counterReducer";

const rootReducer = combineReducers({
    counter: counterReducer
})

export default rootReducer;

4. store > index.js에서 합친 reducer을 import하기

src > index.js( =store )에 rootReducer.js파일을 import하여 저장합니다.

import rootReducer from './Store/index';

const store = configureStore({reducer: rootReducer});
.
.
.

5. Provider로 redner하기

import {Provider} from 'react-redux';
.
const store = configureStore({reducer: rootReducer});

root.render(
  <React.StrictMode>
    <Provider store={store}>
     <App />
    </Provider>
  </React.StrictMode>
);

💡참고하면 좋은 툴
Redux DevTool 브라우저 확장 프로그램 설치 링크 : 애플리케이션의 state 변화 디버깅할 때 사용하는 툴

npm 설치 :

@redux-devtools/extension

import :

import { composeWithDevTools } from '@redux-devtools/extension';

<a href="https://chromewebstore.google.com/detail/fmkadmapgofadopljbjfkapdkoienihi?hl=ko" target:"_blank"> react developer tool 브라우저 확장 프로그램 설치 링크 : 구글 크롬에서 제공하는 리액트 디버깅 툴

[2] Store에 저장된 state 가져오기 (useSelector)

src > store에 App.js 파일을 생성한 뒤, App.js로 들어가 useSelector을 이용해 store에 저장해둔 state를 가져옵니다.

import React from 'react';
import {useSelector} from 'react-redux'

const App = () => {
    // store에 저장된 counter state 가져오기
    const number = useSelector((state) => state.counter.number);

    return (
        <div className='App2'>
            <h1>React Redux Example</h1>
            <h2>{number}</h2>
        </div>
    );
};

export default App;

[3] State 변화주기 (dispatch)

import React from 'react';
import {useSelector, useDispatch} from 'react-redux'
.
.
.

    const dispatch = useDispatch();

    return (
        <div className='App2'>
            <h1>React Redux Example</h1>
            <h2>{number}</h2>
            <button onClick={() => {
                dispatch({type === 'PLUS'})
            }}>PLUS</button>
            <button onClick={() => {
                dispatch({type === 'MINUS'})
            }}>MINUS</button>
        </div>
    );
};

export default App;

📄Reducer의 type이 여러 개일 경우

Reducer의 타입이 여러 개로 사용할 경우 명확하게 지정해주기 위해 아래와 같이 변수에 저장하기도 합니다.

const PLUS = 'counter/PLUS';
const MINUS = 'counter/MINUS';

그리고 각각으 변수들을 이용해 함수를 만들어주면 액션을 발생시킬 때 편리합니다.

export const plus = () => ({type: PLUS});
export const minus = () => ({type: MINUS});

이렇게 함수를 만들면 컴포넌트에서 import해주고, 아래처럼 작성해주면 됩니다.

import {plus, minus} from './counterReducer'

.
.
.

return (
        <div className='App2'>
            <h1>React Redux Example</h1>
            <h2>{number}</h2>
            <button onClick={() => {
                dispatch(plus())
            }}>PLUS</button>
            <button onClick={() => {
                dispatch(minus())
            }}>MINUS</button>
        </div>
    );

📄 두 개의 Reducer을 사용하는 경우

이번에는 reducr을 하나 더 만들어서 두 개의 reducer을 동시에 불러와 사용해보겠습니다.

1. 새로운 reducer 파일 만들기

store > isVisibleReducer.js을 하나 만들어서 reducer 내용을 정의해줍니다.

const initialState = true;

const isVisibleReducer = (state = initialState, action) => {
    if(action.type === 'CHANGE') {
        return !state;
    }
    return state
};

export default isVisibleReducer;

2. Reducer combine 파일에 추가하기

import counterReducer from "./counterReducer";
//새로운 Reudcer
import isVisibleReducer from "./isVisibleReducer";

const rootReducer = combineReducers({
    counter: counterReducer,
    isVisible : isVisibleReducer,
})

export default rootReducer;

3. 두 Reducer을 객체 분해하여 저장 & 변화주기

새로운 App2.js파일을 만들어 두 reducer의 state를 가져와 변화를 줘보겠습니다.

const App2 = () => {
  	//객체 분할하여 저장
    const data = useSelector((state) => {return {money: state.isVisible, number: state.counter.number}});

.
.
.
	//사용 시에도 분할
    <h2>isVisible 값은 "{data.isVisible ? `${data.number}` : '거짓'}"이다</h2>
    <button onClick={() => { 
         dispatch({type: 'CHANGE'})
    }} >Change</button>

결과

App.js 와 App2.js를 화면에 표현해보면 counter의 number값이 동적으로 변하는 것을 볼 수 있습니다.

📄Payload 활용하여 dispatch 시 값 추가하기'

이번에는 dispatch 할 때 값을 추가로 줘보겠습니다.

예를 들어, App.js에서 onChange 이벤트로 전달받은 input값을 disaptch의 payload에 반환해서 sate값을 변경시켜보겠습니다.

1. 새로운 Reducer 만들기

아래와 같이 'money'라는 state에 type이 plus일 때는 payload 'amount'를 더하고, type이 minus일 때는 이를 빼주도록 reducer을 정의해줍니다.

const initialState = {
    money: 0,
}

const banckReducer = (state = initialState, action) => {
    switch(action.type) {
        case 'PLUS':
            return {money: state.money + action.amount};
        case 'MINUS':
            return {money: state.money - action.amount};
        default:
            return state;
    }
};

export default banckReducer;

2. 새로운 reducer을 사용할 수 있도록 rootRduer에 추가하기

import banckReducer from "./banckReducer";

const rootReducer = combineReducers({
    bank : banckReducer,
})

3. App.js에서 onChange 이벤트로 input값 가져와 저장하기

import { useState } from 'react';

.
.
.
const [input, setInput] = useState(0);
const getInput = (e) => {
        setInput(e.target.value)
}

return (
        <div className='App'>
            <h1>은행</h1>
            <h2>잔액: 'money' </h2>
            <input type='text' value={input} onChange={(e) => getInput(e)}></input>
        </div>
    );

4. Stored에 저장한 bank.money를 'money'라는 변수에 저장하기

import {useSelector} from 'react-redux';
.
.
.
const money = useSelector((state) => state.bank.money);

5. '입금'과 '출금' 버튼의 이벤트 함수 작성하기

import {useSelector, useDispatch} from 'react-redux'
.
.
.
const dispatch = useDispatch();

const addMoney = () => {
    dispatch({ type: 'PLUS', amount: Number(input) });
    setInput(0);
};
const minusMoney = () => {
    dispatch({ type: 'MINUS', amount: Number(input) });
    setInput(0);
}

6. button 태그 생성하고 클릭 이벤트 추가하기

   return (
        <div className='App2'>
            <h1>은행</h1>
            <h2>잔액: {money} </h2>
            <input type='text' value={input} onChange={(e) => getInput(e)}></input>
			//각각의 버튼 클릭 시 해당하는 함수 실행
            <button onClick={() => { 
                addMoney()
                }} >입금</button>
            <button onClick={() => { 
                minusMoney()
                }} >출금</button>
        </div>
    );

결과

profile
Done is better than perfect🏃‍♀️

0개의 댓글