Redux saga에서 보통 비동기처리 로직은 worker 함수에서 이루어진다. 다만, 복잡한 비동기처리가 아닌 간단한 처리 (예를 들어 API를 호출하고, put
하는 간단한 작업) 도 적지 않게 발생하는데 이러한 것을 유틸함수로 모듈화하여 관리하면 중복코드를 방지할 수 있다.
todoAPi.loadTodos()
를 호출하고, 요청이 성공했을 때는 loadTodosSuccess
로 put
하고 실패하는 경우에는 loadTodosFail
에 put
하는 worker 함수이다.
function* __loadTodos(action) {
try {
const { data: todos } = yield call(todoApi.loadTodos);
yield put(loadTodosSuccess(todos));
} catch (err) {
yield put(loadTodosFail(err));
}
createWorker
라는 이름으로 work함수를 return
하는 함수를 생성한다. type
과 api호출함수
를 파라미터로 받는다.
export const createWorker = (type, apiCaller) => {
const [SUCCESS, ERROR] = [`${type}Success`, `${type}Fail`];
return function* saga(action) {
try {
const { data } = yield call(apiCaller, action.payload);
yield put({ type: SUCCESS, payload: data });
} catch (err) {
yield put({ type: ERROR, error: true, payload: err });
}
};
};
유틸함수를 이용하여, 아래의 코드로 리팩토링 하였다. loadTodos().type
은 문자열 타입을 반환한다. (redux-toolkit
에서 지원)
const __loadTodos = createWorker(loadTodos().type, todoApi.loadTodos);
프로미스를 다루는 리덕스 모듈 고려사항 (velopert님으로부터...)
- 프로미스가 시작, 성공, 실패했을때 다른 액션을 디스패치해야합니다.
- 각 프로미스마다 thunk 함수를 만들어주어야 합니다. (thunk 사용 시...)
- 리듀서에서 액션에 따라 로딩중, 결과, 에러 상태를 변경해주어야 합니다.
이러한 것을 고려하면서 Reducer를 작성하면 코드가 길어진다.
const initialState = {
todos: {
loading: true,
error: null,
todos: [],
},
todo: {
loading: true,
error: null,
todo: {},
},
};
const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
loadTodos: (state) => {
state.todos.loading = true;
},
loadTodosSuccess: (state, { payload }) => {
state.todos.loading = false;
state.todos.todos = payload;
},
loadTodosFail: (state, { payload }) => {
state.todos.loading = false;
state.todos.error = payload;
},
loadTodoById: (state) => {
state.todo.loading = true;
},
loadTodoByIdSuccess: (state, { payload }) => {
state.todo.loading = false;
state.todo.todo = payload;
},
loadTodoByIdFail: (state, { payload }) => {
state.todo.loading = false;
state.todo.error = payload;
},
},
});
조금이나마 코드를 줄이기 위해 유틸함수를 작성한다.
export const reducerUtils = {
initial: (initialData = null) => ({
loading: true,
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,
}),
};
const initialState = {
todos: reducerUtils.initial([]),
todo: reducerUtils.initial({}),
};
// Slice
const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
loadTodos: (state) => {
state.todos = reducerUtils.loading();
},
loadTodosSuccess: (state, { payload }) => {
state.todos = reducerUtils.success(payload);
},
loadTodosFail: (state, { payload }) => {
state.todos = reducerUtils.error(payload);
},
loadTodoById: (state) => {
state.todo = reducerUtils.loading();
},
loadTodoByIdSuccess: (state, { payload }) => {
state.todo = reducerUtils.success(payload);
},
loadTodoByIdFail: (state, { payload }) => {
state.todo = reducerUtils.error(payload);
},
},
});