2-4-redux-pender
리덕스 펜더는 프로미스 기반 액션들을 관리하기 위한 미들웨어와 도구가 포함되어있는 라이브러리입니다.
작동 방식은 redux-promise-middleware 와 매우 유사합니다. payload 에 프로미스가 있으면 이 프로미스가 시작하기전, 완료, 실패 했을때 뒤에 PENDING, SUCCESS, FAILURE 접미사를 붙여줍니다.
추가적으로, 요청들을 관리하기위한 리듀서와, 요청관련 액션들을 처리하기위한 액션 핸들러 함수들을 생성해주는 도구가 들어있습니다.
자, 그럼한번 사용해볼까요? 우선 설치부터 해줍시다.
$ yarn add redux-pender
이제 적용을 해볼건데요, 기존의 redux-promise-middeware 는 제거해주세요. 작동 방식이 비슷하기 떄문에 서로 충돌 할 수 있습니다. (만약에 동시에 사용해야되는 경우에는 설정을 하여 충돌을 피할 수 는 있습니다. 자세한 사항은 매뉴얼을 참고해주세요. https://github.com/velopert/redux-pender)
src/store.js
import { createStore, applyMiddleware } from 'redux';
import modules from './modules';
import { createLogger } from 'redux-logger';
import ReduxThunk from 'redux-thunk';
import penderMiddleware from 'redux-pender';
/* 로그 미들웨어를 생성 할 때 설정을 커스터마이징 할 수 있습니다.
https://github.com/evgenyrodionov/redux-logger#options
*/
const logger = createLogger();
const store = createStore(modules, applyMiddleware(logger, ReduxThunk, penderMiddleware()));
export default store;
미들웨어를 적용하고 난 다음에는 리듀서를 추가해주세요.
src/modules/index.js
import { combineReducers } from 'redux';
import counter from './counter';
import post from './post';
import { penderReducer } from 'redux-pender';
export default combineReducers({
counter,
post,
pender: penderReducer
});
이 리듀서는 요청들을 관리하는 리듀서입니다. 이 리듀서의 상태는 다음과 같은 구조를 이루고있는데요.
{
pending: {},
success: {},
failure: {}
}
새 프로미스 액션이 디스패치되면 상태가 다음과 같이 변하고:
{
pending: {
'ACTION_NAME': true
},
success: {
'ACTION_NAME': false
},
failure: {
'ACTION_NAME': false
}
}
성공적으로 요청이 완료되면 다음과 같이 변합니다:
{
pending: {
'ACTION_NAME': false
},
success: {
'ACTION_NAME': true
},
failure: {
'ACTION_NAME': false
}
}
요청이 실패한다면, 예상 가능 하시겠죠? :
{
pending: {
'ACTION_NAME': false
},
success: {
'ACTION_NAME': false
},
failure: {
'ACTION_NAME': true
}
}
이 작업을 이 리듀서가 액션 이름에 따라서 해주기때문에 우리가 따로 관리해줄 필요가 없어집니다.
자, 이제 페이지에 들어가서 테스트를 해보세요.
기존의 redux-promise-middleware 를 대체 하였지만, 페이지에 들어가보면 기존 코드는 여전히 제대로 작동 할 것입니다. 작동방식이 서로 비슷하고 뒤에 추가하는 접미사도 (아까 redux-promise-middleware 를 사용할때 커스터마이징을 했기 때문에) 동일하기 때문입니다.
하지만 뭐가 다르냐구요? 액션생성자의 생성 과정과 리듀서에서 액션 처리 과정이 간소화 될 수 있습니다.
다음 코드를 확인하세요:
src/modules/post.js
import { createAction, handleActions } from 'redux-actions';
import { pender } from 'redux-pender';
import axios from 'axios';
function getPostAPI(postId) {
return axios.get(`https://jsonplaceholder.typicode.com/posts/${postId}`)
}
const GET_POST = 'GET_POST';
/* redux-pender 의 액션 구조는 Flux standard action(https://github.com/acdlite/flux-standard-action)
을 따르기 때문에, createAction 으로 액션을 생성 할 수 있습니다. 두번째로 들어가는 파라미터는 프로미스를 반환하는
함수여야 합니다.
*/
export const getPost = createAction(GET_POST, getPostAPI);
const initialState = {
// 요청이 진행중인지, 에러가 났는지의 여부는 더 이상 직접 관리 할 필요가 없어집니다. penderReducer 가 담당하기 때문이죠
data: {
title: '',
body: ''
}
}
export default handleActions({
...pender({
type: GET_POST, // type 이 주어지면, 이 type 에 접미사를 붙인 액션핸들러들이 담긴 객체를 생성합니다.
/*
요청중 / 실패 했을 때 추가적으로 해야 할 작업이 있다면 이렇게 onPending 과 onFailure 를 추가해주면됩니다.
onPending: (state, action) => state,
onFailure: (state, action) => state
*/
onSuccess: (state, action) => { // 성공했을때 해야 할 작업이 따로 없으면 이 함수 또한 생략해도 됩니다.
const { title, body } = action.payload.data;
return {
data: {
title,
body
}
}
}
// 함수가 생략됐을때 기본 값으론 (state, action) => state 가 설정됩니다 (state 를 그대로 반환한다는 것이죠)
})
}, initialState);
어떤가요? 신경써야 할 상태가 줄어들었고, 코드의 길이도 줄어들었죠? 더군다나 리듀서의 가독성도 좋아졌습니다.
최신 버전(v1.2.1) 기준, 다음과 같이 할 수도 있습니다.
import { handleActions } from 'redux-actions';
import { pender, applyPenders } from 'redux-pender';
const initialState = {
post: {}
}
const reducer = handleActions({
// ... some other action handlers...
}, initialState);
export default applyPenders(reducer, [
{
type: LOAD_POST,
onPending: (state, action) => {
return state; // do something
},
onSuccess: (state, action) => {
return {
post: action.payload.data
}
},
onFailure: (state, action) => {
return state; // do something
}
}
])
이제 post 리듀서에서 error 와 pending 값을 관여하지 않게되었으니, 이를 컴포넌트에서도 반영시켜볼까요?
App 컴포넌트의 마지막, connect 하는 부분의 코드만 조금 수정해주면 됩니다.
src/App.js
(...)
export default connect(
(state) => ({
number: state.counter,
post: state.post.data,
loading: state.pender.pending['GET_POST'],
error: state.pender.failure['GET_POST']
}),
(dispatch) => ({
CounterActions: bindActionCreators(counterActions, dispatch),
PostActions: bindActionCreators(postActions, dispatch)
})
)(App);
어떤가요? 웹 요청의 상태관리가 조금은 편해지지 않았나요? 이번 챕터에서 비동기 액션을 처리하는 방식만 3가지를 배웠는데요.
앞으로 우리가 프로젝트를 만들 때는, 비동기 액션을 처리 할 때는 redux-pender 를 사용하겠습니다. 하지만, 이에 관해서는 정해진 답이 없습니다. 이번 챕터에서 다룬것들외에도, redux-observable, redux-saga 등 다른 솔루션들이 있습니다.
어떤 방식을 사용할지는 여러분들의 선택입니다.