저번 파트에서 다뤘던 redux-thunk를 이용해 비동기 요청을 다뤄보았다. 하지만 thunk함수와 로딩 상태를 리듀서에서 관리하려다 보니 코드가 길어져 가독성이 떨어진다. 그래서 반복되는 로직을 분리해 간편하게 작성하고자 한다.
먼저 유틸 함수 파일을 만들어 코드를 작성한다.
lib/createRequestThunk.js
export default function createRequestThunk(type, request) {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return (params) => async (dispatch) => {
dispatch({ type });
try {
const res = await request(params);
dispatch({
type: SUCCESS,
payload: res.data,
});
} catch (e) {
dispatch({
type: FAILURE,
payload: e,
error: true,
});
throw e;
}
};
}
modules/ sample.js
createRequestThunk를 만들어 리듀서 안에서 간단하게 코드 작성을 할 수 있다. 타입(GET_POST,GET_USERS)과 요청(api.getPost,api.getUsers)을 인자로 넣어주어 위에 작성한 함수가 실행이 되고 성공, 실패 요청을 조회할 수 있게 해준다.
import { handleActions } from "redux-actions";
import * as api from "../lib/api";
import createRequestThunk from "../lib/createRequestThunk";
const GET_POST = "sample/GET_POST";
const GET_POST_SUCCESS = "sample/GET_POST_SUCCESS";
const GET_POST_FAILURE = "sample/GET_POST_FAILURE";
const GET_USERS = "sample/GET_USERS";
const GET_USERS_SUCCESS = "sample/GET_USERS_SUCCESS";
const GET_USERS_FAILURE = "sample/GET_USERS_FAILURE";
// createRequestThunk(type, request)
export const getPost = createRequestThunk(GET_POST, api.getPost);
export const getUsers = createRequestThunk(GET_USERS, api.getUsers);
modules/ loading.js
import { createAction, handleActions } from "redux-actions";
const START_LOADING = "loading/START_LOADING";
const FINISH_LOADING = "loading/FINISH_LOADING";
export const startLoading = createAction(
START_LOADING,
(requestType) => requestType
);
export const finishLoading = createAction(
FINISH_LOADING,
(requestType) => requestType
);
const initialState = {};
const loading = handleActions(
{
[START_LOADING]: (state, action) => ({
...state,
[action.payload]: true,
}),
[FINISH_LOADING]: (state, action) => ({
...state,
[action.payload]: false,
}),
},
initialState
);
export default loading;
{
type: 'loading/START_LOADING',
payload: 'sample/GET_POST'
}
요청이 시작되면 위에 있는 액션이 디스패치 되고 sample/GET_POST 상태를 true로 설정해준다. 위에 필드가 없으면 새로 값을 설정해주고 이 요청이 끝나면 다음 액션을 디스패치 해야 한다.
{
type: 'loading/FINISH_LOADING',
payload: 'sample/GET_POST'
}
containers/SampleContainer.js
connect함수 내부를 아래와 같이 변경해준다.
export default connect(
({ sample, loading }) => ({
post: sample.post,
users: sample.users,
loadingPost: loading["sample/GET_POST"],
loadingUsers: loading["sample/GET_USERS"],
// loadingPost: sample.loading.GET_POST,
// loadingUsers: sample.loading.GET_USERS,
}),
{
getPost,
getUsers,
}
)(SampleContainer);
이제 sample 리듀서에서 로딩 중과 관련된 상태를 관리할 필요가 없기 때문에 성공 케이스만 빼고 지워주면 된다.
const sample = handleActions(
{
// [GET_POST]: (state) => ({
// ...state,
// loading: {
// ...state.loading,
// GET_POST: true,
// },
// }),
[GET_POST_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_USERS: false,
},
post: action.payload,
}),
// [GET_POST_FAILURE]: (state, action) => ({
// ...state,
// loading: {
// ...state.loading,
// GET_USERS: false,
// },
// }),
// [GET_USERS]: (state) => ({
// ...state,
// loading: {
// ...state.loading,
// GET_USERS: true,
// },
// }),
[GET_USERS_SUCCESS]: (state, action) => ({
...state,
loading: {
...state.loading,
GET_POST: false,
},
users: action.payload,
}),
// [GET_USERS_FAILURE]: (state, action) => ({
// ...state,
// loading: {
// ...state.loading,
// GET_POST: false,
// },
// }),
},
initialState
);
만약 _FAILURE에 관련된 실패 상태를 처리하려면 컨데이너 컴포넌트에서 try/catch로 에러를 처리해주면 된다.
useEffect(() => {
const fn = async () => {
try {
await getPost(1);
await getUsers(1);
} catch (e) {
console.log(e);
}
};
fn();
}, [getPost, getUsers]);
발췌:
https://github.com/reduxjs/redux-thunk
리액트를 다루는 기술, 김민준