그럼에도 불구하고

👨‍💻

[Redux] Redux middleware란? 본문

React/Redux

[Redux] Redux middleware란?

zenghyun 2023. 5. 9. 17:53

 

오늘은 Redux middleware에 대해 알아보겠습니다.

 

 

 

리액트 웹 애플리케이션에서 API 서버를 연동할 때는 API 요청에 대한 상태도 잘 관리해야 합니다. 예를 들어 요청이 시작되었을 때는 로딩 중임을, 요청이 성공하거나 실패했을 때는 로딩이 끝났음을 명시해야 합니다. 요청이 성공하면 서버에서 받아 온 응답에 대한 상태를 관리하고, 요청이 실패하면 서버에서 반환한 에러에 대한 상태를 관리해야 합니다. 

 

리액트 프로젝트에서 리덕스를 사용하고 있으며 이러한 비동기 작업을 관리해야 한다면, '미들웨어(middleware)'를 사용하여 매우 효율적이고 편하게 상태 관리를 할 수 있습니다. 

 

 

다음은 리덕스를 적용한 간단한 리액트 프로젝트를 준비후, 이 프로젝트를 통해 리덕스 미들웨어에 대해 알아보겠습니다.

 

1. 리덕스를 사용하여 카운터를 구현합니다. 이에 필요한 라이브러리들을 새 프로젝트에 설치해 줍니다.

 

yarn add redux react-redux redux-actions

 

📌 modules/counter.js

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { createAction, handleActions } from "redux-actions";
 
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
 
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
 
const initialState = 0
 
const counter = handleActions({
    [INCREASE]: state => state + 1,
    [DECREASE]: state => state - 1
},
initialState
);
 
export default counter;
cs

 

2. 다음으로 루트 리듀서를 생성합니다. 

 

📌 modules/index.js

 

1
2
3
4
5
6
7
8
9
import { combineReducers } from 'redux';
import counter from './counter'
 
 
const rootReducer = combineReducers({
    counter,
});
 
export default rootReducer;
cs

 

3. 리듀서를 다 만들었으면 src 디렉터리의 index.js에서 스토어를 생성한 후, Provider로 리액트 프로젝트에 리덕스를 적용해야 합니다.

 

📌 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/client';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
 
 
 
 
const store = createStore(rootReducer);
 
sagaMiddleware.run(rootSaga);
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>
);
 
reportWebVitals();
 
cs

 

4. 이어서 카운터 컴포넌트와 카운터 컨테이너 컴포넌트를 만들어줍니다. 

 

📌 components/Counter.js

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
 
const Counter = ({ onIncrease, onDecrease, number }) => {
  return (
    <div>
      <h1>{number}</h1>
      <button onClick={onIncrease}>+1</button>
      <button onClick={onDecrease}>-1</button>
    </div>
  );
};
 
export default Counter;
 
cs

 

📌 containers/CounterContainer.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 { connect } from "react-redux";
import { increase, decrease } from "../modules/counter";
import Counter from "../components/Counter";
 
const CounterContainer = ({ number, increase, decrease }) => {
  return (
    <Counter
      number={number}
      onIncrease={increase}
      onDecrease={decrease}
    />
  );
};
 
export default connect(
  (state) => ({
    number: state.counter,
  }),
  {
    increase,
    decrease,
  }
)(CounterContainer);
 
cs

 

5.  App에서 CounterContainer를 렌더링하여 잘 작동하는지 확인해 줍니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
import './App.css';
import CounterContainer from './containers/CounterContainer';
 
function App() {
  return (
    <>
    <CounterContainer />
    </>
  );
}
 
export default App;
 
cs

 

 


 

 

[ 미들웨어란? ]

리덕스 미들웨어는 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행합니다. 

미들웨어는 액션과 리듀서 사이의 중간자라고 볼 수 있습니다.

 

https://simplehanlab.github.io/redux-middleware

 

리듀서가 액션을 처리하기 전에 미들웨어가 할 수 있는 작업은 여러 가지가 있습니다. 전달받은 액션을 단순히 콘솔에 기록하거나, 전달받은 액션 정보를 기반으로 액션을 아예 취소하거나, 다른 종류의 액션을 추가로 디스패치할 수도 있습니다. 

 

 

 

[ 미들웨어 만들기 ]

실제 프로젝트를 작업할 때 미들웨어를 직접 만들어서 사용할 일은 그리 많지 않습니다. 다른 개발자가 만들어 놓은 미들웨어를 사용하면 되기 때문입니다. 하지만 미들웨어가 어떻게 작동하는지 이해하려면 직접 만들어 보는 것이 가장 효과적입니다. 간단한 미들웨어를 직접 만들어 보면 미들웨어의 작동 방식을 제대로 이해할 수 있습니다. 

 

원하는 미들웨어를 찾을 수 없을 때는 상황에 따라 직접 만들거나 기존 미들웨어들을 커스터마이징하여 사용할 수도 있습니다.

 

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

 

📌 lib/loggerMIddleware.js

 

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

 

미들웨어는 결국 함수를 반환하는 함수를 반환하는 함수입니다. 여기에 있는 함수에서 파라미터로 받아 오는 store는 리덕스 스토어 인스턴스를, action은 디스패치된 액션을 가리킵니다. 이 두가지 값은 익숙한 반면 next의 값은 익숙하지 않습니다.

 

next 파라미터는 함수 형태이며, store.dispatch와 비슷한 역할을 합니다. 하지만 큰 차이점이 있는데, next(action)을 호출하면 그다음 처리해야 할 미들웨어에게 액션을 넘겨주고, 만약 그다음 미들웨어가 없다면 리듀서에게 액션을 넘겨준다는 것입니다. 

 

https://redux-advanced.vlpt.us/1/02.html

 

미들웨어 내부에서 store.dispatch를 사용하면 첫 번째 미들웨어부터 다시 처리합니다. 만약 미들웨어에서 next를 사용하지 않는다면 액션이 리듀서에 전달되지 않습니다. 즉, 액션이 무시되는 것 입니다.

 

이번에 만들 미들웨어는 다음 정보를 순차적으로 콘솔에 보여줍니다.

1. 이전 상태

2. 액션 정보

3. 새로워진 상태 

 

 

📌 loggerMIddleware.js

 

1
2
3
4
5
6
7
8
9
10
11
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;
cs

 

이렇게 만든 리덕스 미들웨어를 스토어에 적용하겠습니다. 미들웨어는 스토어를 생성하는 과정에서 적용합니다.

 

📌 App.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
26
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore, applyMiddleware } from 'redux';
import rootReducer, { rootSaga } from './modules/index';
import { Provider } from 'react-redux';
import loggerMiddleware from './lib/loggerMiddleware';
 
 
 
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(loggerMiddleware));
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>
);
 
reportWebVitals();
 
cs

 

이제 개발자 도구에서 콘솔을 열고 카운터의 버튼을 눌러보면 액션  정보와 업데이트되기 전후의 상태가 잘 나타나게 됩니다. 

 

또한 특정 조건에 따라 액션을 무시하게 할 수도 있고, 특정 조건에 따라 액션 정보를 가로채서 변경한 후 리듀서에게 전달해 줄 수도 있습니다. 아니면 특정 액션에 기반하여 새로운 액션을 여러 번 디스패치할 수도 있습니다.

 

이러한 미들웨어 속성을 사용하여 네트워크 요청과 같은 비동기 작업을 관리하면 매우 유용합니다.

 

[ redux-logger 사용하기 ]

이번에는 오픈 소스 커뮤니티에 올라와 있는 redux-logger 미들웨어를 설치하고 사용해 보겠습니다.

 

yarn add redux-logger

 

이어서 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
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './modules/index';
import { Provider } from 'react-redux';
// import loggerMiddleware from './lib/loggerMiddleware';
import createLogger from 'redux-logger';
 
const logger = createLogger();
const store = createStore(rootReducer, applyMiddleware(logger));
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>
);
reportWebVitals();
 
cs

 

redux-logger를 이용하면 콘솔에 색상도 입혀지고, 액션 디스패치 시간도 나타나게 됩니다. 리덕스에서 미들웨어를 사용할 때는 이렇게 이미 완성된 미들웨어를 라이브러리로 설치해서 사용하는 경우가 많습니다.

 

 

Comments