컴퓨터/React

[Redux] 리액트 리덕스 상태관리 정리/간단한 예제

도도새 도 2023. 8. 8. 18:08

리액트 리덕스 상태관리

 

리덕스에서 상태관리 코드를 작성할 때 리듀서를 작성하게 된다. 리듀서는 디스패치를 호출할 때마다 콜되는 함수이다. 그런데 리듀서 안에서 어떤 것을 실행하게 될까? 보통 리듀서에는 state와 action을 파라미터로 넘기는데, action의 type을 분기로 처리하여 실행할 것을 정하게 된다.

(중요한 것은 state에는 초기 한 번은 실행되므로 디폴트 값을 줘야한다.)

 

const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
};

 

위의 경우, action의 타입이 INCREMENT일 경우 state에 1을 더하고, DECREMENT일 경우 state값에 1을 뺀다.

리액트의 경우 아래 코드처럼 dispatch함수에 액션을 넘기면 리듀서가 실행된다

 

import {useDispatch} from 'react-redux';

let handleDispatch =()=> dispatch(setNameEmail({type:'INCREMENT'}));

 

리듀서인 counterReducer 는 인자로 넘어온 객체 {type:'INCREMENT'}의 타입 값을 읽어 state를 1 더하는 동작을 수행할 것이다.

 

리듀서가 여러개인 경우

리듀서가 여러개인 경우에도 각 리듀서는 지속해서 호출된다. 그렇기에 어느 동작을 수행할 지 명확히 하기 위해 액션 객체의 type값은 필수적으로 작성한다.

 

리듀서가 여러개인 경우 combine을 이용하여 각 리듀서를 묶는다.

 

import {combineReducers} from 'redux'
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
  counter,
  todos
});

export default rootReducer;

 

즉, 위와 같은 형태로 각 리듀서를 묶어버리는 것이다.

이렇게 묶인 리듀서를 아래의 형태로 사용해 스토어를 생성한다.

 

import {Provider} from 'react-redux'
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension'; // 리덕스 개발자 도구

const store = createStore(rootReducer, composeWithDevTools())

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <Provider store={store}>
      <App />
  </Provider>
);

 

Provicer는 앱의 모든 컴포넌트를 스토어와 연결하는 역할을 하게 된다. 또한 해당 스토어에 대한 디스패치, 스토어에 대한 접근 등이 가능하게 된다.

 

let count = useSelector(state => state.counter);

 

예를 들어 위는 state의 counter(combineReducers에서 counter라는 이름으로 등록함)에 접근하여 그 상태값을 받아오는 것이다.

 

 

리액트 리덕스 예제 코드

 

유저 데이터를 등록하고 확인하는 간단한 예제 코드를 작성한다.

 

vite로 타입 스크립트 리액트 프로젝트를 시작한다.

$ npm create vite@latest redux-practice --template react-ts
$ npm run dev

 

src디렉토리에 modules 디렉토리를 만들고 legi.ts를 작성한다.

 

modules/leg.ts

//액션 타입 변수
const SET_NAME_EMAIL = 'legi/SET_NAME_EMAIL';

//반환 값에 대한 인터페이스
export interface user{
    name:string,
    email:string
}

//액션에 대한 인터페이스. 타입을 추가함
export interface userAction extends user {
    type: any;
  }

//액션 생성 함수, 두 개의 파라미터를 받아 액션 타입 객체를 생성함
export const setNameEmail = (name:string, email:string):userAction =>({
    type:SET_NAME_EMAIL,
    name,
    email})

//리듀서에 넣을 초기값
const initialValue = [{name:'', email:''}]

//리듀서 switch로 분기처리를 하며 type이 해당하는 것이 없으면 초기값을 반환
export default function legister(state = initialValue, action:userAction):user[]|undefined{
    switch(action.type){
        case SET_NAME_EMAIL:
            let result:user[]= [
                ...state,
                {
                    name:action.name,
                    email:action.email
                }
            ];
            return result;
        default:
            return state;
    }
}

 

modules/index.js

import {combineReducers} from 'redux'
import legi from './legi'

//리듀서들을 묶는다(여기서는 하나만 사용)
const rootReducer = combineReducers({
  legi,
});

export default rootReducer;

다음으로 src에 components 디렉토리를 만들고 보여질 컴포넌트를 만든다. 굳이 컴포넌트를 나누는 이유는 모든 컴포넌트에서 상태값 사용이 가능하다는 것을 보기 위함이다.

 

src/components/Legi.tsx

import {useDispatch} from 'react-redux';
import {setNameEmail} from '../modules/legi'
import {useState} from 'react'

export default function Legi(){
    const [name, setName] = useState<string>('');
    const [email, setEmail] = useState<string>('');

    let handleChangeName = (evt:any)=>{
        setName(evt.target.value);      
    }

    let handleChangeEmail = (evt:any)=>{
        setEmail(evt.target.value);      
    }

    const dispatch = useDispatch();

//버튼을 누르면 dispatch를 적용한다. 액션 함수가 우선 실행되고, 생성된 액션 객체가 리듀서에 들어가게된다.
     let handleLegi =()=> dispatch(setNameEmail(name, email));

    return(
        <div>
            <input onChange={handleChangeName} type="text" placeholder="이름" />
            <br />
            <input onChange={handleChangeEmail} type="text" placeholder="이메일" />
            <br />
            <button onClick={handleLegi} >등록</button>
        </div>
    )
}

 

src/components/ShowUser.tsx

import {useSelector, } from 'react-redux';
import {user} from '../modules/legi'

export default function ShwoUser(){

//리덕스 state의 값을 받아와서 userInfos에 저장한다. 
//legi라는 이름으로 등록된 리듀서가 관리하는 state를 얻기 위해 legi라는 키로 접근한다.
    let userInfos:user[] = useSelector((state:any)=>{
        return state.legi;
    });

    let comp = userInfos&&userInfos.map((ele, inx)=>{
        return (
            <div key={inx} style={{margin:'1em', backgroundColor:'#e1e1e1'}}>
                이름 : {ele.name}
                <br />
                이메일 : {ele.email}
            </div>
        )
    })

    return(
        <>
        <h1>쇼페이지</h1>
        {comp}
        </>
    )
}

 

main.tsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import rootReducer from './modules/index.ts'
import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(rootReducer, composeWithDevTools())

ReactDOM.createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <App />
  </Provider>,
)

프로바이더 컴포넌트를 사용하여 모든 컴포넌트가 스토어에 접근 가능하도록 한다.

 

App.tsx

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import Legi from './components/Legi'
import ShowUser from './components/ShowUser'

function App() {
  return (
    <>
      <Legi></Legi>
      <ShowUser></ShowUser>
    </>
  )
}

export default App

 

실제 컴포넌트 들을 모아서 랜더링한다.

보아야 할 것은 프롭을 사용하지 않았음에도, Legi에서 등록한 값들이 ShowUser에서 사용 가능하다는 것이다.

결과 화면은 아래와 같다.

 

리덕스 예제

 

위 코드에 대한 깃허브는 아래와 같다.

https://github.com/fkthfvk112/react_practice/tree/main/redux-practice