Redux-Thunk vs Redux-Saga 한국어

bangina·2020년 10월 14일
2

문서 번역

목록 보기
1/1
post-thumbnail

본 게시물은 Medium의 Redux-Thunk vs Redux-Saga을 우리말로 번역한 글입니다. 약간의 의역이 포함되어 있습니다.


💡 Redux-Thunk vs Redux-Saga


요즘에는 많은 동적 웹 애플리케이션에서 비동기 작업을 사용합니다. 당신은 React 개발자로서 아마 경력 내내 Redux-Thunk를 사용해왔을 것입니다. 기존 Redux-Thunk에서 몇년 전부터 유행하고 있는 Redux-Saga와 같은 다른 솔루션으로 이동하는 것이 불편할 수 있습니다. 맞습니다, syntax가 다르기 때문에 처음 접했을 때는 매우 혼란스럽고 이해가 되지 않을 수 있습니다.

Redux-Saga의 이점(Redux-Thunk와 비교하여)은 비동기 데이터 흐름을보다 쉽게 테스트 할 수 있다는 것입니다.

Redux-Thunk는 소규모 프로젝트 및 React에 막 입문한 개발자에게는 좋을 수 있습니다. Redux-Thunk의 로직은 모두 함수 내부에 포함되어 있으며, 더불어 Redux-Thunk을 배우면Redux-Saga의 그 이상하고 생소한 구문도 배울 필요가 없습니다. 그러나 이 튜토리얼에서는 Redux-Thunk에서 → Redux-Saga로 쉽게 이동할 수있는 방법을 보여주고, 위의 이상하고 생소한 구문에 대해 설명해드리겠습니다 :D


가장 먼저 디펜던시를 몇 가지를 설치하겠습니다.

npm i --save react-redux redux redux-logger redux-saga redux-thunk




다음으로 프로젝트에서 Redux를 셋업해야 합니다. Redux 폴더를 만들고 그 안에 store.js 파일을 만들어 보겠습니다.

import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import rootReducer from './root-reducer';
const middlewares = [thunk];
if (process.env.NODE_ENV === 'development') {
  middlewares.push(logger);
}
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;




그 다음에는 root-reducer.js 파일을 만들어줍니다.

import { combineReducers } from 'redux';
import fetchTasksReducer from './reducers/fetchTasksReducer'
const rootReducer = combineReducers({
 tasks: fetchTasksReducer,
});
export default rootReducer;




App.js에 위의 store를 import 해주는 것도 잊지 마시고요.

import React, { Component } from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import Tasks from './components/tasks';
import './App.css';
const store = require('./reducers').init();
class App extends Component {
render() {
return (
  <Provider store={store}>
    <BrowserRouter>
    <div className='App'>
      <div className='container'>
      	<Route exact path='/' component={Tasks} />
      </div>
    </div>
    </BrowserRouter>
  </Provider>
		);
	}
}
export default App;




이제 fetchTasksReducerer를 생성할 차례입니다.

...
const INITIAL_STATE = {
  tasks: null,
  isFetching: false,
  errorMessage: undefined
};
const fetchTasksReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case "FETCH_TASKS_START":
      return {
        ...state,
        isFetching: true
      };
    case "FETCH_TASKS_SUCCESS":
      return {
        ...state,
        isFetching: false,
        tasks: action.payload
      };
    case "FETCH_TASKS_ERROR":
      return {
        ...state,
        isFetching: false,
        errorMessage: action.payload
      };
    default:
      return state;
  }
};
export default fetchTasksReducer;




actiondispatch할 때마다 해당 actionfetchTasksReducer를 통과하고 필요에 따라 state를 업데이트합니다. 이 세 가지 조건들이 작동하는 방식은 다음과 같습니다.

  • FETCH_TASKS_START : HTTP 요청이 시작되었습니다. 예를 들어 사용자에게 진행중인 프로세스가 있음을 알리는 로딩바를 표시하기에 좋은 시간입니다.
  • FETCH_TASKS_SUCCESS : HTTP 요청이 성공했습니다. state를 업데이트해야합니다.
  • FETCH_TASKS_ERROR : HTTP 요청이 실패했습니다. 사용자에게 문제를 알리기 위해 오류 구성 요소를 표시 할 수 있습니다.


Redux-thunk


현재는 reducer가 처리할 action을 실행하는 액션 생성자(의역 : 액션객체 생성함수)가 없기 때문에 앱이 원하는 대로 작동하고 있지 않습니다.

export const fetchTasksStarted = () => ({
  type:  "FETCH_TASKS_START"
});
export const fetchTasksSuccess = tasks => ({
  type: "FETCH_TASKS_SUCCESS",
  payload: tasks
});
export const fetchTasksError = errorMessage => ({
  type: "FETCH_TASKS_ERROR",
  payload: errorMessage
});
const fetchTasks =  () => async dispatch => {
    dispatch(fetchTasksStarted())
    try{
        const TaskResponse = await fetch("API URL")
const task = await taskResponse.json()
        dispatch(fetchTasksSuccess(tasks))
    }catch(exc){
        dispatch(fetchTasksError(error.message))
    }
}



fetchTasks가 처음에는 이상하게 보일 수 있지만, fetchTasksdispatch 매개변수를 가진 함수를 반환하는 함수입니다. dispatch가 호출되면 제어 흐름이 reducer로 이동하여 어떤 작업을 수행할지 결정됩니다. 위의 경우에는 요청이 성공한 경우에만 애플리케이션의 state를 업데이트합니다.

Redux-saga

Redux-saga는 Redux로 비동기 코드를 쉽게 구현할 수있는 redux 미들웨어입니다. Redux-Thunk의 가장 막강한 경쟁자입니다.

시작해봅시다. 위의 코드에서 Redux-Thunk 도입 이전까지의 동일한 코드를 가지고 진행해보겠습니다. Store.jsRedux-saga 미들웨어를 구현해보겠습니다.

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import logger from 'redux-logger';
import rootReducer from './root-reducer';
import { watchFetchTasksSaga } from './saga/fetchTasks.saga';
const sagaMiddleware = createSagaMiddleware();
const middlewares = [logger, sagaMiddleware];
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
sagaMiddleware.run(watchFetchTasksSaga);
export default store;




이제 actions를 만들어볼 차례입니다.

export const fetchTasksStarted = () => ({
  type:  "FETCH_TASKS_START"
});
export const fetchTasksSuccess = tasks => ({
  type: "FETCH_TASKS_SUCCESS",
  payload: tasks
});
export const fetchTasksError = errorMessage => ({
  type: "FETCH_TASKS_ERROR",
  payload: errorMessage
});




saga폴더를 만들고 그 안에 fetchTasks.saga파일을 생성합니다.

import { takeLatest, put } from "redux-saga/effects";
function* fetchTasksSaga(){
 try {
const taskResponse = yield fetch("API URL")
const tasks = yield taskResponse.json()
yield put(fetchTasksSuccess(tasks));
} catch (error) {
yield put(fetchTasksError(error.message));
  }
}
export default function* watchFetchTasksSaga(){
    yield takeLatest("FETCH_TASKS_START", fetchTasksSaga)
}



위의 함수들을 생성기 함수(generator functions)라고 합니다.
takeLatestandtakeEvery를 모두 사용할 수 있지만 여기서는 takeLatest를 사용했습니다.
(마지막 이벤트만 불러들이는 한)

put 함수를 호출하면 dispatch에서 했던 것처럼 action을 실행할 수 있습니다. putreduceraction을 처리하도록 조절해줍니다.

...
const INITIAL_STATE = {
  tasks: null,
  isFetching: false,
  errorMessage: undefined
};
const fetchTasksReducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case "FETCH_TASKS_START":
      return {
        ...state,
        isFetching: true
      };
    case "FETCH_TASKS_SUCCESS":
      return {
        ...state,
        isFetching: false,
        tasks: action.payload
      };
    case "FETCH_TASKS_ERROR":
      return {
        ...state,
        isFetching: false,
        errorMessage: action.payload
      };
    default:
      return state;
  }
};
export default fetchTasksReducer;



결론

여기까지입니다. 이제 당신은ReactRedux에서 비동기 작업을 사용하는 두 가지 접근 방식에 익숙하게 됐습니다. 당신이 작업중인 프로젝트에 따라 둘 중 어느 것을 선택할지 결정할 수 있습니다.

profile
🥨 UX, Graphic에 관심이 많은 주니어 프론트엔드 개발자. 이모지 Lover 💘

0개의 댓글