본문 바로가기

WEB/JavaScript

[Redux] 미들웨어란? (Redux Thunk, redux-promise-middleware)

미들웨어

미들웨어는 소프트웨어 각 분야에서 세부적으로 다르게 뜻한다. 위키백과에서는 OS와 소프트웨어 중간에서 조정과 중개 역할을 하는 중간 소프트웨어라고 말한다.

유의해야할건 소프트웨어 분야에서는 같은 이름을 가지고 있지만 다른 것을 의미 하는 것들이 상당히 많다.

 

Redux Thunk나 Redux Saga나 Redux Promise 등은 Redux 에서 동작하는 미들웨어이다.

어떠한 두 가지 요소의 중간에서 동작하는 소프트웨어라고 생각하면 크게 보았을 때 그 의미가 대략 같다.

Redux 미들웨어

리덕스가 가지는 상태관리 라이브러리들 중 핵심 기능 중 하나는 미들웨어를 사용할 수 있다는 점이다.

Context API나 Mobx에 경우에는 미들웨어를 지원하지 않는다.

 

리액트에서 리덕스의 미들웨어를 사용하기 위해선 index.js에서 store를 설정해주는 과정에서 미들웨어를 사용하는 것을 선언할 수 있다.

 

import { applyMiddleware, createStore } from 'redux';

const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore)

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

 

redux 라이브러리에서 applyMiddleware 함수에 사용할 미들웨어들을 추가해준 뒤에 Store에 추가해주면 된다.

글쓴이는 promiseMiddleware, ReduxThunk를 추가해주었다.(물론 미들웨어도 npm으로 install 해야 한다.)

 

 

리덕스의 동작 순서는 액션이 dispatch가 된후 리듀서를 호출 하는데 기존에 있던 상태(state)를 dispatch한 액션을 바꾼다.

이때 리듀서가 호출되어 상태를 바꾸기 이전에 동작하는 것이 미들웨어이다.

리덕스에 미들웨어는 대표적으로 Redux Thunk와 Redux-Saga 등이 있다.

 

보통 리덕스로 전역 상태로 관리하는 것은 컴포넌트 전역으로 쓰이는 상태(데이터)들을 관리한다. 그런 상태(데이터)는 보통 DB에 저장되어 있기 때문에 API 통신으로 백엔드를 통해서 가져온다.

그렇기에 리덕스로 액션 생성자를 통해 가져오는 데이터들은 대개 비동기 통신으로 가져오는 경우가 많기 때문에 미들웨어가 쓰이는 것이다.

 

예를 들어 클라이언트(리액트)에서 백엔드 API를 연동해야 된다면, Redux Thunk와 redux-promise-middleware 를 사용할 수 있다.

Redux Thunk

Redux Thunk는 액션 생성자가 리턴하는 것을 객체가 아닌 함수를 사용할 수 있게 한다. 그리고 함수를 리턴하면 그 함수를 실행이 끝난 뒤에 값을 액션으로 넘겨준다.

 

정리하자면, 기존에 액션 생성자가 리턴하는 객체로는 처리하지 못했던 비동기 작업을 Redux Thunk를 사용하면서 일반 함수를 리턴할 수 있게 됨에 따라 일반 함수에서 가능한 모든 동작들이 가능해진다. 그중에 비동기 통신 작업을 할 수 있어 사용하는 것이다. Redux Thunk가 비동기 통신을 위해 만들어진 것이 아니라 액션 생성자가 함수를 리턴할 수 있다는 것에 좀 더 초점을 맞추어야 될 것 같다.

 

export const login = (dataToSubmit) => async dispatch => {
  const response = await axios.post(`api/users/login`, dataToSubmit)
 
  dispatch({ type: 'LOGIN', payload: response })
}

 

위 코드가 Redux Thunk를 사용하여 비동기 통신을 수행하는 코드이다.

login 액션 생성자가 dispatch가 되어 가지고 온 파라미터로 Post 요청을 보낸 뒤에 서버에서 응답 해준 값을 액션으로 다시 리듀서에 호출되어 전달해준다.

이때 주목해야할 점은 reponse는 Promise 객체이다. 미들웨어를 사용하지 않는 경우라면 그대로 통신을 완료하지 않은 Promise 객체를 payload로 보내겠지만

async await에 사용으로 동기적으로 사용할 수 있게 해준다 즉, Promise 객체가 통신이 완료되고 응답을 받을때까지 기다렸다가 payload에 실어서 리듀서에 전달할 수 있는 것이다. 

 

그렇다면 비동기 통신에만 집중한 미들웨어를 사용하고 싶다면 redux-pomise가 있다. Promise 객체에 처리에 있어서는 Redux-Thunk와 거의 동일하다고 보면 되지만 Redux Thunk는 함수를 반환할 수 있다는 것에 좀 더 포커스를 맞쳐야한다.

Redux Promise

export const userInfo = (body) => {
    const request = axios.post('/api/users/userlist', body)
        .then(response => response.data);
    
    return {
        type: USER_INFO,
        payload: request
    }
}

 

위 코드는 userInfo를 서버에 API 요청을 보내어 응답을 받은 뒤 리듀서에 Promise 통신 결과를 액션으로 넘기는 액션 생성자이다.

 

여기서 중요한 점은 리듀서에 넘겨주는 request는 Promise 객체이다. 다시 말해 store의 상태를 반영 하기에는 부적절한 타입이다.

원래라면 action을 바로 리듀서에게 넘기면 되지만 Promise 객체는 통신 결과를 가져와야지만 가능하다.

 

미들웨어를 사용하지 않고 Promise 객체를 리듀서에 넘겨줄 순 없다. 비동기 작업을 위한 객체를 액션 생성자가 어떻게 처리하겠는가?

방법 중 하나는 위 섹션에서 설명한 Redux Thunk를 사용해 비동기 통신을 끝낸 뒤에 dispatch 해주는 방법이 있고 지금 설명할 redux-promise-middleware를 사용하는 방법이 있다.

 

redux-promise를 사용하면 payload 되는 객체가 만약 Promise 객체라면 통신 결과가 나올 때까지 기다린 이후 결과 값을 리듀서에게 전달한다. 여기서 주목해야 하는 점은 미들웨어를 사용하지 않으면 그냥 Promise 객체 그 자체를 payload로 보낸다.

실제 console.log를 출력하면 Promise 객체에 대한 값이 출력된다. 하지만 redux-promise를 사용한다면 Promise를 객체가 통신이 끝난 뒤 그 값을 payload로 응답해준다. 이것이 redux-pomise-middleware에 기능이다.

 

리덕스 비동기 통신은 하기 위해서는 다양한 방법이 있고 요새는 꼭 리덕스가 아니더라도 SWR이 뜨고 있다.

리덕스 특유에 복잡함은 익히 알려진 바 있고 아마 대체되지 않을까 싶다.