리덕스 미들웨어 비동기 작업 관리


middleware 란?

리덕스 미들웨어는 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행합니다.
액션과 리듀서 사이에 중간자라고 볼 수 있습니다.

미들웨어

미들웨어 만들기

실제 프로젝트에서는 다른 개발자가 만들어 놓은 미들웨어를 사용하면 되지만,
미들웨어가 어떻게 작동하는지 이해하려면 직접 만들어보는 것도 좋습니다.

액션이 디스패치될 때마다 액션의 정보와 액션이 디스패치되기 전후의 상태를 콘솔에 보여주는 로깅 미들웨어를 작성해 봅니다.

1
2
3
4
5
const loggerMiddleware = (store) => (next) => (action) => {
//미들웨어 기본 구조
};

export default loggerMiddleware;

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

함수의 파라미터에서 store는 리덕스 스토어 인스턴스를, action은 디스패치된 액션을 가리킵니다.

반면에 nextstore.dispatch와 비슷한 역활을 하지만 next(action)을 호출하면 그 다음 처리해야 할 미들웨어에게 액션을 넘겨주고, 만약 다음 미들웨어가 없다면 리듀서에게 액션을 넘겨주게 됩니다.
next를 호출하지 않게 된다면 액션이 무시되어 리듀서에게 전달되지 않습니다.

미들웨어
미들웨어는 위와 같은 구조로 작동합니다. 리덕스 스토어에는 여러 개의 미들웨어를 등록할 수 있습니다.
새로운 액션이 디스패치 되면 첫 번째로 등록한 미들웨어가 호출됩니다.
만약에 미들웨어에서 next(action)을 호출하게 되면 다음 미들웨어로 액션이 넘어갑니다.
그리고 미들웨어 내부에서 store.dispatch 를 사용하면 처음부터 다시 액션이 디스패치 되는 것 이기 때문에 현재 미들웨어를 다시한번 처리하게 됩니다. 다른 액션을 추가적으로 발생시킬 수 도 있습니다.

미들웨어를 마저 구현해 보겠습니다.

loggerMiddleware.js
1
2
3
4
5
6
7
8
9
10
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;

리덕스 미들웨어 작성이 끝났으면 스토어에 적용해야 합니다.
스토어에 미들웨어를 적용 할 때는 applyMiddleware 라는 함수를 사용합니다.

index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './modules';
import loggerMiddleware from './lib/loggerMiddleware';

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

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();

미들웨어에서는 여러 종류의 작업을 처리할 수 있습니다. 특정 조건에 따라 액션을 무시할 수도 있고, 액션 정보를 가로채서 변경한 후 리듀서에게 전달할 수도 있습니다. 또 특정 액션에 따라 새로운 액션을 여러 번 디스패치 할 수도 있습니다.

이러한 미들웨어 속성은 네트워크 요청등의 비동기 작업을 관리할 때 매우 유용합니다.

redux-logger 사용

다른 개발자가 만든 미들웨어 사용 예시.

오픈 소스 커뮤니티에 올라와 있는 redux-logger 미들웨어를 설치하여 적용해 봅니다.

yarn add redux-logger
npm i redux-logger

index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import logger from 'redux-logger';

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

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

serviceWorker.unregister();

위와 같이 적용해 주면됩니다.

Redux DevTool 을 사용한다면 redux-logger 는 사실 쓸모가 없습니다. Redux Devtool 이 이미 그 기능을 갖추고있고 훨씬 강력하기 때문이죠. 하지만 Redux Devtool 을 사용하지못하는 환경이라면 redux-logger 는 매우 유용한 미들웨어입니다

Redux-DevTools 사용

Redux DevTools 를 미들웨어와 함께 사용할 때 코드 작성방법을 알아봅니다.
Redux DevTools Extension 메뉴얼

index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules';
import logger from 'redux-logger';
import { composeWithDevTools } from 'redux-devtools-extension';

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

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


serviceWorker.unregister();

비동기 작업 처리 미들웨어

리덕스 미들웨어를 사용하여 비동기 작업을 더욱 효율적으로 관리합니다.

redux-thunk

yarn add redux-thunk
npm i redux-thunk

redux-thunk는 리덕스에서 비동기 작업을 처리 할 때 가장 많이 사용하는 미들웨어입니다.
redux-thunk는 리덕스의 창시자인 Dan Abramov가 만들었으며, 리덕스 공식 매뉴얼에서도 비동기 작업을 처리하기 위하여 미들웨어를 사용하는 예시를 보여줍니다.

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

thunk 란?

thunk란, 특정 작업을 나중에 하도록 미루기 위해서 함수형태로 감싼것을 칭합니다.

예를 들어

1
const x = 1 + 2;

이 코드가 실행되면 1 + 2 의 연산이 바로 진행됩니다.

1
const foo = () => 1 + 2;

이렇게 하면 foo()가 호출될 때 연산이 이뤄짐으로 특정 작업을 나중에 하도록 미룰 수 있습니다.

redux-thunk 미들웨어는 객체 대신 함수를 생성하는 액션 생성함수를 작성 할 수 있게 해줍니다.
리덕스에서는 기본적으로는 액션 객체를 디스패치합니다.
일반 액션 생성자는 파라미터를 가지고 액션 객체를 생성하는 작업만합니다

특정 액션이 몇초뒤에 실행되게 하거나, 현재 상태에 따라 아예 액션이 무시되게 하려면, 일반 액션 생성자로는 할 수가 없습니다. 하지만, redux-thunk 는 이를 가능케합니다.

redux-thunk 라이브러리를 사용하면 thunk 함수를 만들어 디스패치할 수 있고
리덕스 미들웨어가 thunk 함수를 전달받아 store의 dispatch와 getState를 파라미터로 넣어 호출합니다.

적용하기

index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './modules';
import logger from 'redux-logger';
import ReduxThunk from 'redux-thunk';

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

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();

Thunk 생성 함수 작성

redux-thunk는 액션 생성 함수에서 일반 액션 객체를 반환하는 대신에 함수를 반환합니다.

작성 예시
1
2
3
4
5
6
7
8
9
10
export const increaseAsync = () => (dispatch) => {
setTimeout(() => {
dispatch(increase());
}, 1000);
};
export const decreaseAsync = () => (dispatch) => {
setTimeout(() => {
dispatch(decrease());
}, 1000);
};