Redux Saga Modularization

With·2021년 9월 16일
0

Redux saga에서 보통 비동기처리 로직은 worker 함수에서 이루어진다. 다만, 복잡한 비동기처리가 아닌 간단한 처리 (예를 들어 API를 호출하고, put하는 간단한 작업) 도 적지 않게 발생하는데 이러한 것을 유틸함수로 모듈화하여 관리하면 중복코드를 방지할 수 있다.

기존

todoAPi.loadTodos() 를 호출하고, 요청이 성공했을 때는 loadTodosSuccessput하고 실패하는 경우에는 loadTodosFailput하는 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하는 함수를 생성한다. typeapi호출함수를 파라미터로 받는다.

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);

추가

Reducer Modularization

프로미스를 다루는 리덕스 모듈 고려사항 (velopert님으로부터...)

  1. 프로미스가 시작, 성공, 실패했을때 다른 액션을 디스패치해야합니다.
  2. 각 프로미스마다 thunk 함수를 만들어주어야 합니다. (thunk 사용 시...)
  3. 리듀서에서 액션에 따라 로딩중, 결과, 에러 상태를 변경해주어야 합니다.

이러한 것을 고려하면서 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);
    },
  },
});
profile
주니어 프론트엔드 개발자 입니다.

0개의 댓글