본문 바로가기

오픈소스/노드

[Node] React 정리(23) - 리덕스 미들웨어 [redux-thunk, redux-sega]

리덕스 미들웨어는 리덕스가 지니고 있는 핵심 기능입니다. Context API 또는 MobX를 사용하는것과 차별화가 되는 부분이죠.

 

 

리덕스 미들웨어를 사용하면 액션이 디스패치 된 다음, 리듀서에서 해당 액션을 받아와서 업데이트하기 전에 추가적인 작업을 할 수 있습니다.

 

여기서 언급한 추가적인 작업들은 다음과 같은 것들이 있습니다.

 

  • 특정 조건에 따라 액션이 무시되게 만들 수 있습니다.
  • 액션을 콘솔에 출력하거나, 서버쪽에 로깅을 할 수 있습니다.
  • 액션이 디스패치 됐을 때 이를 수정해서 리듀서에게 전달되도록 할 수 있습니다.
  • 특정 액션이 발생했을 때 이에 기반하여 다른 액션이 발생되도록 할 수 있습니다.
  • 특정 액션이 발생했을 때 특정 자바스크립트 함수를 실행시킬 수 있습니다.

 

보통 리덕스에서 미들웨어를 사용하는 주된 사용 용도는 비동기 작업을 처리 할 때 입니다. 예를 들어 리액트 앱에서 우리가 만약 백엔드 API 를 연동해야 된다면, 리덕스 미들웨어를 사용하여 처리하곤 하죠.

 

리덕스 미들웨어는 누구든지 만들어서 사용 할 수 있습니다만, 일반적으로는 리덕스 미들에웨어 라이브러리를 설치하여 사용합니다. 비동기 작업에 관련된 미들웨어 라이브러리는 redux-thunkredux-sagaredux-observableredux-promise-middleware 등이 있습니다.

 

redux-saga와 redux-observable의 경우엔 특정 액션을 모니터링 할 수도 있으므로, 특정 액션이 디스패치됐을때 원하는 함수를 호출하거나, 또는 라우터를 통해 다른 주소로 이동하는 것이 가능합니다. 이 튜토리얼에서는 미들웨어를 사용하여 비동기작업을 처리하는 방법을 배우게 될 때, redux-thunk와 redux-saga만을 다루게 됩니다(이 두 라이브러리가 가장 많이 사용됩니다).

 

기본 프로젝트는 아래 프로젝트를 참고해주시기 바랍니다.

https://github.com/libtv/React_Note/tree/master/24.%20react-redux

 

GitHub - libtv/React_Note: This is a notebook organized by React.

This is a notebook organized by React. Contribute to libtv/React_Note development by creating an account on GitHub.

github.com

 

# Contents


  • 미들웨어 직접 만들기
  • redux-logger
  • redux-thunk
  • redux-sega

 

 

# 미들웨어 직접 만들기


이번 섹션에서는 미들웨어를 직접 만들어보도록 하겠습니다. 사실 실무에서는 리덕스 미들웨어를 직접 만들게 되는 일은 거의 없습니다. 하지만, 한번 직접 만들어보게 된다면 미들웨어가 어떤 역할인지 훨씬 쉽게 이해 할 수 있습니다.

 

리덕스 미들웨어를 만들 땐 다음 템플릿을 사용합니다.

const middleware = store => next => action => {
  // 하고 싶은 작업...
}

 

미들웨어는 결국 하나의 함수입니다. 함수를 연달아서 두번 리턴하는 함수죠. 화살표가 여러번 나타나는게 도대체 뭐지, 하고 헷갈릴 수도 있을텐데요, 이 함수를 function 키워드를 사용하여 작성한다면 다음과 같습니다.

 

function middleware(store) {
  return function (next) {
    return function (action) {
      // 하고 싶은 작업...
    };
  };
};

 

이제 여기서 각 함수에서 받아오는 파라미터가 어떤 것을 의미하는지 알아보겠습니다.

 

첫번째 store는 리덕스 스토어 인스턴스입니다. 이 안에 dispatch, getState, subscribe 내장함수들이 들어있죠.

두번째 next 는 액션을 다음 미들웨어에게 전달하는 함수입니다. next(action) 이런 형태로 사용합니다. 만약 다음 미들웨어가 없다면 리듀서에게 액션을 전달해줍니다. 만약에 next 를 호출하지 않게 된다면 액션이 무시처리되어 리듀서에게로 전달되지 않습니다.

세번째 action 은 현재 처리하고 있는 액션 객체입니다.

 

그럼 미들웨어를 직접 작성을 해봅시다! src 디렉터리에 middlewares 라는 디렉터리를 만들고, myLogger.js 라는 파일을 다음과 같이 작성해보세요.

 

Middlewares/myLogger.js

const myLogger = (store) => (next) => (action) => {
    console.log(action);
    const result = next(action);
    return result;
};

export default myLogger;

 

이제 미들웨어를 스토어에 적용해봅시다! 스토어에 미들웨어를 적용 할 때에는 applyMiddleware 라는 함수를 사용합니다.

index.js를 다음과 같이 수정해보세요.

 

 

index.js 의 전체 코드입니다.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { rootReducer } from "./moduels";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import myLogger from "./middleware/myLogger";

const store = createStore(rootReducer, applyMiddleware(myLogger));

ReactDOM.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </React.StrictMode>,
    document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

이런식으로 사용한다는 것 보다는 라이브러리를 사용하여 로그를 출력하는 방안이 더 나아보입니다. redux-logger 라는 라이브러리를 설치 및 적용을 하고, 또 미들웨어를 사용하게 되는 경우에는 Redux DevTools를 어떻게 사용하는지 알아보도록 하겠습니다.

 

 

# redux-logger


이번 섹션에서는 redux-logger 라이브러리를 설치하고, 설치한 라이브러리를 사용하는 방법에 대해 알아보도록 하겠습니다. 먼저 redux-logger 라이브러리를 아래와 같은 명령어를 통해 설치해주시기 바랍니다.

 

라이브러리를 아래 명령어를 통해 설치합니다.

npm install --save redux-logger

 

그 다음에 index.js 에서 불러와서 적용을 해보겠습니다. 리덕스에 미들웨어를 적용 할 때에는 다음과 같이 여러개의 미들웨어를 등록 할 수 있답니다.

 

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { rootReducer } from "./moduels";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import myLogger from "./middleware/myLogger";
import logger from 'redux-logger'

const store = createStore(rootReducer, applyMiddleware(myLogger, logger));

ReactDOM.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </React.StrictMode>,
    document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

# redux-thunk


redux-thunk는 리덕스에서 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어입니다. 이 미들웨어를 사용하면 액션 객체가 아닌 함수를 디스패치 할 수 있습니다. redux-thunk는 리덕스의 창시자인 Dan Abramov가 만들었으며, 리덕스 공식 매뉴얼에서도 비동기 작업을 처리하기 위하여 미들웨어를 사용하는 예시를 보여줍니다. 구문의 예시는 아래와 같습니다.

 

const getComments = () => async (dispatch, getState) => {
  const id = getState().post.activeId;
  dispatch({ type: 'GET_COMMENTS' });
  try {
    const comments = await api.getComments(id);
    dispatch({ type:  'GET_COMMENTS_SUCCESS', id, comments });
  } catch (e) {
    dispatch({ type:  'GET_COMMENTS_ERROR', error: e });
  }
}

 

그러면 본격적으로 redux-thunk의 사용법을 알아보도록 하겠습니다. 

 

라이브러리를 아래 명령어를 통해 설치합니다.

npm install --save redux-thunk

 

그 다음엔, redux-thunk를 index.js 에서 불러와서 applyMiddlewares를 통해 적용해보세요.

 

아래 코드는 index.js 의 내용입니다.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { rootReducer } from "./moduels";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import logger from "redux-logger";
import ReduxThunk from "redux-thunk";

const store = createStore(rootReducer, applyMiddleware(ReduxThunk, logger));

ReactDOM.render(
    <React.StrictMode>
        <Provider store={store}>
            <App />
        </Provider>
    </React.StrictMode>,
    document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

아래 코드는 modules/counter.js 코드입니다.

//* action type *//
const INCREASE = "conuter/INCREASE";
const DECREASE = "conuter/DECREASE";

//* create action */
export function increase() {
    return {
        type: INCREASE,
    };
}

export function decrease() {
    return {
        type: DECREASE,
    };
}

export function asyncIncrease() {
    return function (dispatch, getState) {
        setTimeout(() => {
            dispatch(increase());
        }, 1000);
    };
}

export function asyncDecrease() {
    return function (dispatch, getState) {
        setTimeout(() => {
            dispatch(decrease());
        }, 1000);
    };
}

//* initialize variable0 */
const initializeState = 0;

//* reducer */
export default function conuter(state = initializeState, action) {
    switch (action.type) {
        case INCREASE:
            return state + 1;
        case DECREASE:
            return state - 1;
        default:
            return state;
    }
}

 

아래 코드는 containers/CounterContainer.js 코드입니다.

import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { increase, decrease, asyncDecrease, asyncIncrease } from "../moduels/counter";
import Counter from "../components/Counter";

export default function CounterContainer() {
    const state = useSelector((state) => {
        return state.conuter;
    });

    const dispatch = useDispatch();

    const onIncrease = () => {
        dispatch(asyncIncrease());
    };

    const onDecrease = () => {
        dispatch(asyncDecrease());
    };

    return (
        <div className="counter_container">
            <Counter onDecrease={onDecrease} onIncrease={onIncrease} number={state}></Counter>
        </div>
    );
}

 

타임아웃을 통해 1초 후에 증가 혹은 감소가 된다는 것을 확인할 수 있었습니다.