Redux 미들웨어

taehyung·2023년 11월 17일

React.js

목록 보기
19/24

Redux 저장소는 비동기 논리에 대해 아무것도 모릅니다. 동기적으로 작업을 전달하는 방법, 루트 리듀서 함수를 호출하여 상태를 업데이트하는 방법, 변경된 내용을 UI에 알리는 방법만 알고 있습니다. 모든 비동기성은 저장소 외부에서 발생해야 합니다.

앞서 우리는 Redux 리듀서가 절대로 "부작용"을 포함해서는 안 된다고 말했습니다. "부작용"은 함수에서 값을 반환하는 것 외에 볼 수 있는 상태 또는 동작의 변경입니다 . 일반적인 종류의 부작용은 다음과 같습니다.

  • 콘솔에 값 로깅 - 콘솔에 찍히는 값의 데이터 양이 달라 질 수 있다?
  • 파일 저장 - 어떤 파일 저장될지 모름
  • 비동기 타이머 설정 - 정확한 시간에 작동한다는 보장이 없음. 미세한 오차여도 멱등하지 아니하다. 타이머는 오차가있음
  • AJAX HTTP 요청 만들기 -
  • 함수 외부에 존재하는 일부 상태 수정 또는 함수에 대한 인수 변경
  • 난수 또는 고유한 난수 ID 생성(예: Math.random()또는 Date.now())

위와 같은 행위를 사이드 이펙트 라고 합니다.

리듀서 함수의 내부에서는 외부 세계의 변화를 주는 행위는 절대로 하면 안된다는 규칙이 있습니다.
그러나 실제 앱은 어딘가 에서 이런 종류의 작업을 수행해야 합니다 . 그렇다면 리듀서에 부작용을 넣을 수 없다면 어디에 넣을 수 있을까요 ?
바로 리덕스 미들웨어 입니다.

미들웨어란?

리덕스 미들웨어는 액션을 디스패치 했을 때, 리듀서에서 처리하기전에 사전에 지정된 작업들을 수행하는 전처리기 같은 녀석입니다.

액션 - 디스패치 - 미들웨어 - 리듀서 - 스토어 반영 순서로 처리됩니다. 미들웨어로 할 수 있는 작업의 종류는 다양합니다.

실제 프로젝트에서 미들웨어를 직접 만들어서 사용할 일은 많지 않습니다. 다른 개발자가 만들어놓은 미들웨어를 사용하는 일이 더 많기 때문이죠.
그렇기 때문에 이 포스팅에선 직접 만들어보면서 미들웨어를 이해하면 됩니다.

미들웨어 기본 구조

const middleware = store => next => action => {
};

const middleware = (store) => {
	return (next)=>{
      return (action)=>{
      }
    }
}

//두 함수는 정확히 같은 행위를 합니다.

미들웨어는 결국 함수를 반환하는 함수의 연속입니다.


store:

store는 리덕스 인스턴스를 가지고 있으며, dispatch 함수도 가지고있습니다.

미들웨어 내부에서 store.dispatch를 사용하면 첫번째 미들웨어부터 다시 처리합니다.
미들웨어 내부에서 store.dispatch 를 사용하는 대표적인 예시는 비동기 처리 후 상태를 업데이트하는 경우 입니다.

next:

  • 함수이며, 파라미터로 action을 받습니다.
  • store.dispatch 와 비슷한 역할을 합니다.
  • next(action)을 호출하면 다음 미들웨어 혹은 리듀서에게 액션을 전달합니다. action 을 전달할 다음 미들웨어가 없으면 리듀서에게 넘겨줍니다.

만약, 미들웨어에서 next를 사용하지 않으면 액션이 리듀서에게 전달되지 않습니다.즉, 액션이 무시됩니다.

미들웨어에서 next() 메서드는 빠져서는 안되는 반드시 1회 이상 사용해야하는 함수입니다.

action:


액션은 디스패치된 액션을 가지고 있습니다.


미들웨어 핵심 개념

리덕스 미들웨어는 액션이 디스패치되고, 리듀서 함수가 실행되기 전에 실행된다.그러므로 액션의 정보, 스토어의 상태 또한 알 수 있다.

  • 미들웨어는 일반 함수다.
  • 만든 함수는 스토어에게 제공하고 미들웨어로 지정한다.
    미들웨어로 지정된 함수는 이제 액션이 디스패치 된 후에 작동합니다.
  • 미들웨어로 지정된 함수는 디스패치된 액션과 스토어의 상태를 알 수 있다.

미들웨어 함수 만들기

// store,action 에 접근할 수 있다.
const loggerMiddleware = (store) => (next) => (action) => {
  console.group(action && action.type); //액션 타입으로 log를 그룹화
  console.log("이전 상태", store.getState());
  console.log("액션", action);
  next(action); // 다음 미들웨어 혹은 리듀서에게 액션 전달
  
  console.log("다음 상태", store.getState()); //업데이트 된 상태
  console.groupEnd(); // 그룹 끝
};

export default loggerMiddleware;

스토어에게 미들웨어 함수 제공하기

import loggerMiddleware from "./lib/loggerMiddleware";
import { applyMiddleware, createStore } from "redux";

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

결과

위 사진은 redux-logger 라이브러리 사용 사진입니다.

만들어낸 미들웨어 함수의 next(action) 부분은 다음 미들웨어가 없기 때문에 리듀서 함수를 실행시켰고, 상태 변경이 끝난 후 console.log 를 사용하기때문에 리듀서 함수가 끝난 후의 상황도 알 수 있습니다. 굉장하죠?


미들웨어로 비동기 처리하기

비동기 작업을 처리하는데 도움을 주는 오픈소스 미들웨어들이 있습니다.

  • redux-thunk : 비동기 작업을 처리할 때 가장많이 사용하며 특징으로는 객체가 아닌 함수 형태의 액션을 디스패치 할 수 있게 해줍니다.

  • redux-saga : thunk 다음으로 가장 많이 사용되는 비동기관련 라이브러리 입니다. 특징으로는 특정 액션이 디스패치 되었을 때, 정해진 로직에 따라 다른 액션을 디스패치시키는 규칙을 작성하여 비동기 작업을 처리할 수 있게 해 줍니다.


redux-thunk

redux-thunk는 미들웨어의 한 종류 입니다. 액션이 디스패치되면 이 미들웨를 반드시 거쳐가겠죠?

중요
redux 의 action 은 기본적으로 객체 형태이고, 객체형태가 아닌 액션을 dispatch 하게되면 에러를 내뿜습니다.
redux-thunk의 핵심은 함수형태의 action을 디스패치 할 수 있게 해주는 기능입니다. 아래 코드는 redux-thunk의 코드의 핵심 내용입니다.

function createThunkMiddleware(extraArgument) {
  var middleware = function middleware(_ref) {
    var dispatch = _ref.dispatch,
      getState = _ref.getState;
    return function (next) {
      return function (action) { 
        if (typeof action === "function") { //액션이 함수일 때 분기처리
          return action(dispatch, getState, extraArgument);
        } /*액션이 함수일 때 store의 dispatch, getState 메소드를 만들어둔 thunk함수에 전달 및 실행하고, extraArgument 는 dispatch 된 액션 함수에게 파라미터로 전달된 값입니다.*/

        return next(action);
      };
    };
  };

  return middleware;
}

주석과 코드를 잘 봐주세요.

중요
어떠한 함수가 디스패치되면 Thunk 미들웨어에 의해 dispatch, getState, extraArgument 를 파라미터로 받는 함수가 됩니다.

그리고 디스패치된 함수 내부에서는 비동기 처리를 하거나, 다양한 행위를 할 수 있게되죠.

간단한 카운터 예제로 예를들어보겠습니다.

thunk 미들웨어 설치

npm i redux-thunk

thunk 미들웨어 store 에 적용

import thunk from "redux-thunk";
import { applyMiddleware, createStore } from "redux";
import { Provider } from "react-redux";


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

<Provider store={store}>
	<App />
</Provider>

Thunk 함수 만들기
Thunk 함수를 만든다는 것은, 함수를 리턴하는 함수를 만든다는 말입니다. 그래야 Thunk 미들웨어에 의해 Store에서 제공되는 메서드를 받아서 쓸 수 있습니다.

import { useDispatch } from "react-redux";

const dispatcher = useDispatch();
const INCREASE = {type:'INCREASE'}

function increaseAsync(payload){
	return function(dispatch, getState){ //thunk 에 의해 스토어로부터 전달받은 스토어전용 메소드 입니다.
      console.log(payload, getState)
      setTimeout(()=>{
    	dispatch(INCREASE) //디스패치를 통해 상태를 변경할 수 있습니다.
      })
    }
}

function onClick(){
  dispatcher(increaseAsync('payload'));
}

<button onClick={onClick}>+<button> // + 버튼

이해를 돕기위해 ES5 문법을 사용하고, 하나의 파일에 모두 작성한 점 양해 부탁드립니다. 또한 현재는 리듀서쪽 코드는 안봐도 됩니다.

thunk 미들웨어 핵심

  • thunk는 함수를 디스패치 할 수 있도록 구현되어있는 미들웨어다.
  • thunk 전용 함수는 함수 내부에서 store내장 함수인 dispatch, getState 를 사용할 수 있다.
  • 함수를 디스패치하면 thunk 미들웨어가 스토어 내장함수를 제공하기 때문이다.
  • thunk 전용함수는 일반 함수이기 때문에 내부에서 무엇이든 할 수 있다.
profile
Front End

0개의 댓글