// thunk함수
// dispatch함수 실행 시 인자로 넣어서 사용한다
// dispatch(getPosts()) - 반환값이 함수 - thunk가 실행시켜서 반환값을 다음 미들웨어에 넘김
export const getPosts = () => async dispatch => {
dispatch({ type: GET_POSTS }); // 요청이 시작됨 - 상태 바꾸는 action
try {
const posts = await postsAPI.getPosts(); // API 호출
dispatch({ type: GET_POSTS_SUCCESS, posts }); // 성공 - 상태 바꾸는 action
} catch (e) {
dispatch({ type: GET_POSTS_ERROR, error: e }); // 실패 - 상태 바꾸는 action
}
};
리덕스 모듈 리팩토링하기
// src/lib/asyncUtils.js
//기본 액션이름 //api함수
export const createPromiseThunk = (type, promiseCreator) => {
const [SUCCESS, ERROR] = [`${type}_SUCCESS`, `${type}_ERROR`];
return param => async dispatch => {
// 요청 시작
dispatch({ type, param });
try {
const payload = await promiseCreator(param);
dispatch({ type: SUCCESS, payload });
} catch (e) {
dispatch({ type: ERROR, payload: e, error: true });
}
};
};
// 파라미터를 넣어서 원하는 상태의 객체를 반환하도록
export const reducerUtils = {
initial: (initialData = null) => ({
loading: false,
data: initialData,
error: null
}),
loading: (prevState = null) => ({
loading: true,
data: prevState,
error: null
}),
success: payload => ({
loading: false,
data: payload,
error: null
}),
error: error => ({
loading: false,
data: null,
error: error
})
};
export const handleAsyncActions = (type, key) => {
const [SUCCESS, ERROR] = [`${type}_SUCCESS`, `${type}_ERROR`];
return (state, action) => {
switch (action.type) {
case type:
return {
...state,
[key]: reducerUtils.loading()
};
case SUCCESS:
return {
...state,
[key]: reducerUtils.success(action.payload)
};
case ERROR:
return {
...state,
[key]: reducerUtils.error(action.payload)
};
default:
return state;
}
};
};
적용
// action의 기본 이름과 api함수만 넣으면 된다
export const getPosts = createPromiseThunk(GET_POSTS, postsAPI.getPosts);
export const getPost = createPromiseThunk(GET_POST, postsAPI.getPostById);
// 반환되는 객체를 함수를 실행해 사용할 수 있다
const initialState = {
posts: reducerUtils.initial(),
post: reducerUtils.initial()
};
export default function posts(state = initialState, action) {
switch (action.type) {
case GET_POSTS:
case GET_POSTS_SUCCESS:
case GET_POSTS_ERROR:
return handleAsyncActions(GET_POSTS, 'posts')(state, action);
case GET_POST:
case GET_POST_SUCCESS:
case GET_POST_ERROR:
return handleAsyncActions(GET_POST, 'post')(state, action);
default:
return state;
}
}
라우트 설정 시 문제점
포스트 목록 재로딩 문제 해결하기
// 최초 한 번 실행하고 data가 없을 때마다 요청
useEffect(() => {
if (data) return;
dispatch(getPosts());
}, [data, dispatch]);
로딩을 새로하긴 하는데, 로딩중... 을 띄우지 않는 것
사용자에게 좋은 경험을 제공하면서도 뒤로가기를 통해 다시 포스트 목록을 조회 할 때 최신 데이터를 보여줄 수 있다
// 받아온 데이터가 있다면 loading을 띄우지 않고 전 데이터 유지 후 새로 받아옴
export const handleAsyncActions = (type, key, keepData = false) => {
const [SUCCESS, ERROR] = [`${type}_SUCCESS`, `${type}_ERROR`];
return (state, action) => {
switch (action.type) {
case type:
return {
...state,
[key]: reducerUtils.loading(keepData ? state[key].data : null)
};
case SUCCESS:
return {
...state,
[key]: reducerUtils.success(action.payload)
};
case ERROR:
return {
...state,
[key]: reducerUtils.error(action.error)
};
default:
return state;
}
};
};