programming language/react

[React] Redux-thunk 알아보기

공대키메라 2022. 6. 26. 14:44

지난 시간에 Redux code splitting 을 combine reducer로 어떻게 적용하는지 알아보았다. 

(궁금하면 여기 클릭!)

 

이번에는 정말로 Redux thunk와 Redux Saga에 대해 알아보고 적용할 것이다. 

 

필자가 참고한 사이트는 다음과 같다. 

 

참고:

https://www.redhat.com/ko/topics/middleware/what-is-middleware

https://react.vlpt.us/redux-middleware/

https://redux.js.org/usage/writing-logic-thunks

https://redux.js.org/usage/writing-logic-thunks#thunk-overview

https://react.vlpt.us/redux-middleware/04-redux-thunk.html

https://www.freecodecamp.org/news/redux-thunk-explained-with-examples/

https://github.com/reduxjs/redux-thunk

 

 

 

1. middleware(미들웨어)?

Redux Thunk 와 Redux Saga에 대해 알기 전에 미들웨어(middleware)란 무엇인지 알아야 한다. 

 

middleware란 말 그대로 중간에서 어떤 작업을 도와줄 것 같은 느낌이다. 

 

Red Hat 사이트에서는 미들웨어에 대해서 다음과 같이 소개한다. 

 

미들웨어는 운영 체제에서 제공하지 않는 일반적인 서비스와 기능을 애플리케이션에 제공하는 소프트웨어입니다.

데이터 관리, 애플리케이션 서비스, 메시징, 인증 및 API 관리는 주로 미들웨어를 통해 처리됩니다.

출처 : https://www.redhat.com/ko/topics/middleware/what-is-middleware

 

필자가 middleware를 적용하고자 하는 곳은 React의 reducer이다. 

 

출처 : https://i.imgur.com/31tvphE.png

이 middleware를 통해 reducer의 기능을 추가 및 개선할 수 있다. 즉, 기능을 향상시킨것이다. 

 

리덕스 미들웨어는 redux만 가지고 있는, Context API 또는 MobX를 사용하는것과 차별화가 되는 부분이다.

 

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

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

오! 정말 많은 기능을 우리가 스스로 적용할 수 있지만 이미 이러한 기능을 쉽게 구현해 놓은 것이 Redux-thunk와 Redux-saga이다. 

 

필자는 지난 시간에 사용한 프로젝트에 logger 기능을 가진 middleware를 직접 적용할 것이다. 

2. Logging 기능을 가진 middleware 적용하기 

그러면 logging 기능을 한번 적용해보도록 하겠다.

 

진짜 별 거 없다!

 

최종 프로젝트 모습은 다음과 같다. 

지난 시간에 사용했던 프로젝트에서 myLogger 폴더와 myLogger.js가 추가되었다.

 

 

index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/index";
import myLogger from "./myLogger/myLogger.js";

const store = createStore(reducer, applyMiddleware(myLogger));
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

myLogger/myLogger.js

const myLogger = (store) => (next) => (action) => {
  console.log(action); // 먼저 액션을 출력합니다.
  const result = next(action); // 다음 미들웨어 (또는 리듀서) 에게 액션을 전달합니다.
  return result; // 여기서 반환하는 값은 dispatch(action)의 결과물이 됩니다. 기본: undefined
};

export default myLogger;

적용해서 실행한 모습은 다음과 같다. 아~ 너무쉬워! ㅎㅎ

 

 

현재 store => next => action으로 myLogger middleware가 적혀있는데 다음 과정을 거치는 것이다. 

 

출처 : https://i.imgur.com/fZs5yvY.png

 

이렇게 우리는 middleware가 무엇인지, 어떻게 적용하는지 이해했다... S2

3. Redux Thunk 알아보기 

우선 thunk가 무엇인지에 대한 설명을 공식사이트에서 확인할 수 있다.

 

Thunk란?

단어 "thunk"는 몇몇 지연된 일(some delayed work)을 하는 코드의 조각들을 의미하는 프로그래밍 용어이다.

로직을 지금 실행하기보다는 함수 body, 혹은 나중에 작동하도록 하는 코드를 작성할 수있다.

특히 Redux에서, "thunk"는 Redux store의 dispatch와 getState 메소드들과 함께 상호작용할 수 있는 내부 로직을 가진 함수의 패턴이다. 

thunk를 사용하려면 redux-thunk middleware가 필요한데, 이것은 Redux store에 하나의 configuration 부분으로 더해진다.

thunk는 Redux 어플리케이션에서 비동기적 로직을 작성하기 위한 표준 접근이자, 흔히 data를 받아오기 위해 사용된다. 

하지만, 다양한 업무에 사용될 수있고, 동기,  비동기 로직을 둘다 포함할 수 있다.

출처 : https://redux.js.org/usage/writing-logic-thunks#thunk-overview

 

redux-thunk는 리덕스에서 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어다.

 

이 미들웨어를 사용하면 액션 객체가 아닌 함수를 디스패치 할 수 있다.

 

그러면 왜 함수를 디스패치(dispatch)해야 하는가?

 

전에 작성한 코드를 다시 한번 보자

src/reducers/number.js

const initialState = {
  number: 0,
};

const ADD_NUMBER = "ADD_NUMBER";
const SUB_NUMBER = "SUB_NUMBER";
const MUL_NUMBER = "MUL_NUMBER";
const DIV_NUMBER = "DIV_NUMBER";

export const addNumberAction = (data) => {
  return {
    type: ADD_NUMBER,
    data,
  };
};

export const subNumberAction = (data) => {
  return {
    type: SUB_NUMBER,
    data,
  };
};

export const mulNumberAction = (data) => {
  return {
    type: MUL_NUMBER,
    data,
  };
};

export const divNumberAction = (data) => {
  return {
    type: DIV_NUMBER,
    data,
  };
};

export const selectNumber = (state) => {
  return state.number.number;
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_NUMBER:
      return {
        ...state,
        number: state.number + action.data,
      };
    case SUB_NUMBER:
      return {
        ...state,
        number: state.number - action.data,
      };
    case MUL_NUMBER:
      return {
        ...state,
        number: state.number * action.data,
      };
    case DIV_NUMBER:
      return {
        ...state,
        number: state.number / action.data,
      };
    default:
      return state;
  }
};

export default reducer;

 

현재는 예시를 위해서 이렇게 작성됐지만, 만약 back단에서 데이터를 가져오고 현재 front 화면으로 데이터를 가져오는 작업을 하게 된다면...

 

이러한 과정은 비동기적으로 이루어질 것이다. 요청을 해서, 요청을 처리하고, 요청 결과를 반환해주는 작업이 필요할 것이다. 

 

즉, 위의 코드처럼 바로 type을 변경하는것에 그치지 않고 추가적인 작업이 내부에서 필요하다는 사실이다. 

 

그럼 아까 작업하던 프로젝트에 다음과 같이 적용하도록 하겠다.

 

$ npm i redux-thunk

 

그리고  코드들을 다음과 같이 수정한다. '

index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { applyMiddleware, createStore } from "redux";
import reducer from "./reducers/index";
// import myLogger from "./myLogger/myLogger.js";
import ReduxThunk from "redux-thunk";

const store = createStore(reducer, applyMiddleware(ReduxThunk));
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <Provider store={store}>
    <App />
  </Provider>
);

src/reducers/index.js

import { combineReducers } from "redux";
import number from "./number";
import add100 from "./add100";
const rootReducer = combineReducers({
  add100,
  number,
});

export default rootReducer;

src/reducers/number.js

const initialState = {
  number: 0,
};

const ADD_NUMBER = "ADD_NUMBER";
const SUB_NUMBER = "SUB_NUMBER";
const MUL_NUMBER = "MUL_NUMBER";
const DIV_NUMBER = "DIV_NUMBER";

export const addNumberAction = (data) => {
  return {
    type: ADD_NUMBER,
    data,
  };
};

export const subNumberAction = (data) => {
  return {
    type: SUB_NUMBER,
    data,
  };
};
export const mulNumberAction = (data) => ({ type: MUL_NUMBER, data });

export const mulNumberActionThunk = (data) => {
  return (dispatch, getState) => {
    const state = getState();
    dispatch(mulNumberAction(data));
  };
};

export const mulNumberAsync = () => (dispatch) => {
  setTimeout(() => dispatch(mulNumberActionThunk(2)), 1000);
};

export const divNumberAction = (data) => {
  return {
    type: DIV_NUMBER,
    data,
  };
};

export const selectNumber = (state) => {
  return state.number.number;
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_NUMBER:
      return {
        ...state,
        number: state.number + action.data,
      };
    case SUB_NUMBER:
      return {
        ...state,
        number: state.number - action.data,
      };
    case MUL_NUMBER:
      return {
        ...state,
        number: state.number * action.data,
      };
    case DIV_NUMBER:
      return {
        ...state,
        number: state.number / action.data,
      };
    default:
      return state;
  }
};

export default reducer;

 

기존에 logger 기능을 넣은 middleware를 만들었다.

 

그리고 그것을 적용했는데 이번에는 thunk를 redux의 "applyMiddleware" 함수로 넘겨준다. 

 

그리고 reducers폴더 내의 number.js에서 mulNumberAction을 생성한다. 

 

그 내부에서 setTimeOut을 작동시키도록 했다.

 

그리면 multiply button을 클릭할 때 마다 1초 뒤에 곱해지는 것을 확인할 수 있다. 

 

 

실제 redux-thunk에 대한 정보를 담은 github 사이트를 참고하면 정말 별거 없다. 

 

간단한 코드로 어마무시한 스타를 받았으니, 굳이 어려운 코드가 아니라도 사용하기 좋고 간단하다면 별을 많이 받을 수 있을 것이다!


사실 이번 공부에서 redux-thunk와 redux-saga를 둘다 공부하려고 했는데 

 

redux-thunk에 대해서 제대로 알려보려고 한 결과 생각보다 너무 길어진 관계로

 

다음 글에서 redux-saga를 적용하는 법에 대해서 공부하겠다.