React를 다루는 기술 18 - 리덕스 미들웨어 - 01
리액트 어플리케이션에서 API 서버를 연동할 때는 API 요청에 대한 상태관리도 잘 해야한다.
요청이 시작되면 로딩 중임을 나타내고 요청이 성공하거나 실패했을 때에는 로딩이 끝났음을 명시해야하며 서버로부터 온 응답을 관리하고 혹여 에러가 발생하면 이를 알리고 기록해야한다.
리액트 어플리케이션에서 리덕스를 이용해 이러한 비동기 작업을 진행하고 있다면 미들웨어를 통해 이를 관리할 수 있다.
미들웨어
미들웨어는 액션을 디스패치 했을 때 이를 리듀서로 넘겨주기 전에 지정된 작업을 실행하는 액션과 리듀서 사이의 중간자이다.
미들웨어 만들기
일반적으로 작업할 때는 미들웨어를 직접 만들기 보다는 이미 만들어져있는 것들을 사용하게 되지만
원하는 미들웨어가 존재하지 않거나 만들어져있는 미들웨어를 조금 수정하여 사용해야 하는 경우를 위해서 미들웨어를 어떻게 생성하고 동작하는지 알아둘 필요가 있다.
const loggerMiddleware = store => next => action => {
// 미들웨어의 기본 구조
}
export default loggerMiddleware;
위의 코드가 미들웨어의 기본구조가 되는데 해당 화살표 함수를 풀어쓰면 이렇게 된다.
const loggerMiddleware = function loggerMiddleware(store) {
return function(next) {
return function(action) {
// 미들웨어 기본 구조
}
}
}
결국 미들웨어란 함수를 반환하는 함수를 반환하는 함수이다.
여기서 store는 리덕스 스토어 인스턴스를 action은 디스패치 된 액션을 가리키며 next는 다음 미들웨어에게 액션을 넘겨주는 역할을 한다.
만약 next로 액션을 넘겨줬는데 다음 미들웨어가 없다면 리듀서로 액션이 넘어가면서 미들웨어는 종료된다.
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;
위 코드는 미들웨어의 흐름을 브라우저 콘솔 로그로 확인할 수 있는 로그로 위에서부터 차례대로 이전상태가 next를 통해 리듀서로 액션이 넘어가서 상태가 업데이트되는 과정을 확인할 수 있다.
이러한 로그를 자동으로 콘솔에 표시해주는 라이브러리로 redux-logger가 있으며 이를 통해 위의 콘솔로그보다 더 많은 정보를 콘솔을 통해서 확인할 수 있다.
redux-thunk
비동기 작업을 처리하는 미들웨어로는 대표적으로 redux-thunk가 있다.
thunk
redux-thunk를 사용하기 위해서는 우선 thunk가 무엇인지를 이해해야하는데 thunk란 특정 작업을 나중에 진행할 수 있도록 함수형태로 감싼것을 뜻한다.
const addOne = x => x + 1;
const addOneThunk = x => () => addOne(x);
const fn = addOneThunk(1);
setTimeout(() => {
const value = fn(); // 해당 시점에서 실행
console.log(value);
}, 1000);
위 코드는 x에 1을 더하는 함수를 파라미터 1을 넣은 시점에 바로 실행되는 것이 아니라
fn이라는 함수에 담아서 원하는 타이밍에 비동기적으로 실행되도록 하는 코드로 여기서 addOne이라는 함수를 함수로 한 번 더 감싸주는 것이 thunk이다.
redux-thunk에서는 이러한 thunk함수를 만들어서 디스패치 할 수 있는데 이를 통해 리덕스 미들웨어가 해당 함수를 전달받아 dispatch와 getState를 파라미터로 넣어 호출해준다.
const sampleThunk = (dispatch, getState) => {
}
Thunk 생성 함수 만들기
import { createAction, handleActions } from 'redux-actions';
import { delay, put, takeLatest, select, throttle } from 'redux-saga/effects';
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
export const increaseAsync = () => dispatch => {
setTimeout(() => {
dispatch(increase());
}, 1000)
}
export const decreaseAsync = () => dispatch => {
setTimeout(() => {
dispatch(decrease());
}, 1000)
}
[....]
위의 increaseAsync와 decreaseAsync는 1초 후에 액션을 디스패치하도록 하는 thunk 생성함수이다.
이를 통해 리덕스를 통해 increaseAsync와 decreaseAsync를 호출하면 1초 후에 비동기적으로 상태를 업데이트해주게 된다.
웹 요청 비동기 작업 처리하기
axios등을 이용하여 웹 요청 비동기 작업을 진행할 때에도 redux-thunk를 사용할 수 있다.
export const getPost = id => async dispatch => {
dispatch({ type: GET_POST }); // 시작됨
try {
const response = await api.getPost(id);
dispatch({
type: GET_POST_SUCCESS,
payload: response.data
}); // 성공
} catch (e) {
dispatch({
type: GET_POST_FAILURE,
payload: e,
error: true
}); // 에러 발생
throw e;
}
}
위의 코드는 getPost 함수를 호출 시 dispatch({ type: GET_POST })
를 통해 이 비동기 작업이 시작되었다는 상태를 업데이트하고 axios를 통해 받은 응답이 성공이면 상태를 업데이트하고 실패일 경우 에러를 표시하는 thunk 생성함수의 기본형태이다.
하지만 모든 요청에 대해 모두 이렇게 긴 함수를 작성한다면 코드가 너무 길어질 염려가 있기 때문에 한 줄로 처리할 수 있는 유틸함수를 사용한다.
import { startLoading, finishLoading } from '../modules/loading';
export default function createRequestThunk(type, request) {
// 성공 및 실패 액션 타입을 정의합니다.
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return params => async dispatch => {
dispatch({ type }); // 시작됨
dispatch(startLoading(type));
try {
const response = await request(params);
dispatch({
type: SUCCESS,
payload: response.data
}); // 성공
dispatch(finishLoading(type));
} catch (e) {
dispatch({
type: FAILURE,
payload: e,
error: true
}); // 에러 발생
dispatch(startLoading(type));
throw e;
}
};
}
// 사용법: createRequestThunk('GET_USERS',api.getUsers);
이를 통해 코드 상에서는 createRequestThunk함수에 액션타입과 비동기 웹 호출 함수만 담아주면 한 줄로 thunk 생성함수를 만들어낼 수 있다.
출처자료
https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=209354690 (리액트를 다루는 기술(개정판))