본문 바로가기
JavaScript/React

React) 리덕스 미들웨어를 통한 비동기 작업 관리 - 미들웨어란?

by 박채니 2022. 12. 19.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

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

 

리액트 웹 애플리케이션에서 API 서버를 연동할 때는 상태 관리를 잘 해줘야 합니다.

요청이 시작되었을 때로딩 중임을, 요청이 성공/실패했을 때로딩이 끝났음을 명시해주어야 합니다.

요청이 성공하면 서버에서 받아 온 응답에 대한 상태를 관리하고, 요청이 실패하면 서버에서 반환한 에러 상태를 관리합니다.

 

리덕스 사용 시 이 같은 비동기 작업을 관리해야 한다면, 미들웨어를 사용해 효율적으로 관리할 수 있습니다.

 


작업 환경 준비

 

CRA 사용해 리액트 프로젝트 생성

$ yarn create react-app learn-redux-middleware

 

리덕스 설치

$ yarn add redux react-redux redux-actions

 

카운터를 구현할 예정이므로, counter 리덕스 모듈을 작성해주겠습니다.

modules/counter.js

import { createAction, handleActions } from 'react-actions';

// 액션 타입 정의
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

// 액션 생성 함수
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);

// 초기 상태 정의
const initialState = 0; // 상태를 꼭 객체일 필요 X 

// 리듀서 작성
// handleActions 첫번째 파라미터: 각 액션에 대한 업데이트 함수, 두번째 파라미터: 초기값
const counter = handleActions(
    {
        [INCREASE]: state => state + 1,
        [DECREASE]: state => state - 1
    },
    initialState
);

// 만일 handleActions 사용하지 않는다면?
// function counter(state = initialState, action) {
//     switch(action.type) {
//         case INCREASE: state + 1;
//         case DECREASE: state - 1;
//         default: return state;
//     }
// }

export default counter;

 

루트 리듀서 생성

modules/index.js

import counter from "./counter";
import { combineReducers } from 'redux';

const rootReducer = combineReducers({
    counter
});

export default rootReducer;

 

스토어 생성 후 리액트 프로젝트에 리덕스 적용

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { createStore } from 'redux';
import { Provider } from 'react';
import rootReducer from './modules';

const store = createStore(rootReducer);

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

 

카운터 컴포넌트 생성

components/Counter.js

const Counter = ({ number, onIncrease, onDecrease }) => {
    return(
        <div>
            <h1>{number}</h1>
            <button onClick={onIncrease}>+1</button>
            <button onClick={onDecrease}>-1</button>
        </div>
    )
}

export default Counter;

 

카운터 컨테이너 컴포넌트 생성

containers/CounterContainer.js

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);

 

App.js

import CounterContainer from './containers/CounterContainer';

function App() {
  return (
    <div>
      <CounterContainer />
    </div>
  );
}

export default App;

잘 렌더링되는 것을 확인할 수 있습니다.

 


리덕스 미들웨어란?

- 액션을 디스패치했을 때 리듀서에서 이를 처리하기에 앞서 사전에 지정된 작업들을 실행

- 액션과 리듀서 사이의 중간자

 

리듀서가 액션을 처리하기 전에 미들웨어가 할 수 있는 작업은?

  • 전달받은 액션을 단순히 콘솔에 기록
  • 전달받은 액션 정보를 기반으로 액션을 취소
  • 전달받은 액션 정보를 기반으로 다른 종류의 액션을 추가로 디스패치 등

 

미들웨어 만들기

 

실제 프로젝트에서는 직접 미들웨어를 만들기 보단, 이미 만들어져 있는 미들웨어를 사용한다고 합니다.

 

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

 

src/lib/loggerMiddleware.js

const loggerMiddleware = store => next => action => {
    // 미들웨어 기본 구조
};

export default loggerMiddleware;

// 일반 function 키워드 사용 시
const loggerMiddleware = function LoggerMiddleware(store) {
    return function(next) {
        return function(action) {
            // 미들웨어 기본 구조
        };
    };
};

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

 

파라미터 의미

  • store
    - 리덕스 스토어 인스턴스
    - dispatch, getState, subscribe 내장 함수를 지님
  • next
    - store.dispatch와 비슷한 역할을 하는 함수 형태
    - next(action) 형태로 호출
    - 액션을 다음 미들웨어에게 넘겨줌
    - 미들웨어가 없다면 리듀서에게 액션을 넘겨줌
    - next를 호출하지 않으면 액션이 리듀서에 전달되지 않음 (액션 무시)
    - store.dispatch 사용 시 첫 번째 미들웨어부터 다시 처리
  • action
    - 디스패치된 액션 (현재 처리하고 있는 액션 객체)

 

생성할 미들웨어는 다음 정보를 순차적으로 콘솔에 보여줍니다.

  1. 이전 상태
  2. 액션 정보
  3. 새로워진 상태

loggerMiddleware.js

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;

 

스토어 적용

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
import LoggerMiddleware from './lib/LoggerMiddleware';

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

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

생성하였던 loggerMiddleware 미들웨어를 스토어에 적용시켜 주었습니다.

미들웨어는 스토어를 생성하는 과정에서 적용하므로, createStore에 같이 적용해줍니다.

액션 정보 및 업데이트 전후 상태가 잘 찍히는 것을 확인할 수 있습니다.

 

redux-logger 사용하기

 

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

 

$ yarn add redux-logger

 

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './modules';
import { Provider } from 'react-redux';
import { createLogger } from 'redux-logger';

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

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

콘솔에 색상도 입혀지고, 액션 디스패치 시간도 나타나는 것을 확인할 수 있습니다.