Redux thunk vs Redux Saga

기록일기📫·2021년 4월 19일
9
post-thumbnail

최근에 진행하는 프로젝트들에서 thunk와 saga를 둘 다 써볼 기회가 있었다. 두 가지를 모두 사용해보며 두 미들웨어가 어떤 차이가 있는지 정확히 정리해놓고 싶어서, 포스팅을 통해 정리해 놓기로 했다.😎

Redux middleware를 이용한 비동기 통신

Redux의 모든 과정은 동기적으로 발생하기 때문에, 비동기 통신을 통해 받아 온 결과값으로 state를 업데이트 하기 위해서는 외부 미들웨어 라이브러리를 사용해야 한다.

제일 많이 사용되는 미들웨어로는 Redux-thunk와 Redux-saga가 있다. 두 미들웨어의 동작 방식은 조금 다르지만, 결과적으로는 특정 action이 dispatch 됐을 때 비동기 로직 처리 후 reducer로 전달해 주는 방식으로 비동기 처리를 지원한다는 공통점이 있다.

쉽게 말하면 reducer로 가기 전에 middleware가 코드를 가로채서 비동기 로직을 처리한다고 생각 하면 된다! 그러면 두 middleware가 어떤 차이가 있는지에 조금 더 자세히 살펴보도록 하자😊

Redux-thunk

먼저 redux-thunk에 대해 알아보자. thunk란 특정 작업을 나중에 할 수 있도록 미루기 위해 함수 형태로 감싼 것을 의미한다.

Redux에서는 기본적으로 action 객체를 dispatch하여 특정 action을 reducer에 전달한다. 그렇지만 Redux-thunk 미들웨어를 사용하면 action 객체 뿐만 아니라, thunk 함수를 만들어서 dispatch 할 수 있다.

즉 thunk 미들웨어에서는 action 안에 type과 payload 뿐만 아니라 API 요청이나 비동기 처리 로직도 들어갈 수 있다.

말로 설명하면 어려우니, 코드를 보면서 이해해보자.

const increment = () => { // action 객체
  return {
    type: INCREMENT_COUNTER
  };
}

const incrementAsync = () => {
  return dispatch => { // dispatch 를 파라미터로 가지는 함수를 반환
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 1000);
  };
}


store.dispatch(incrementAsync()); // thunk 함수를 dispatch!

https://github.com/reduxjs/redux-thunk 의 코드를 참조하였습니다.

위 코드를 보면 incrementAsync 함수를 통해 increment action을 1초 뒤에 dispatch하도록 비동기 로직을 처리하고 있는 것을 볼 수 있다.

기존 action 생성 함수에서 action 객체만 생성 했다면, redux-thunk를 쓰면 위의 incrementAsync 같이 함수를 생성하는 액션 생성함수를 만들수 있게 된다. 즉 기존의 동기적인 로직을 처리하는 방식과 크게 다르지 않게 비동기적인 로직을 처리할 수 있다.

이러한 유연성은 thunk의 장점이 되기도 하지만, 어떨 때는 객체를 반환하고 또 어떨 때는 함수를 반환하기 때문에 action 객체가 복잡해지게 된다는 단점으로 작용하기도 한다.

Redux-saga

다음으로는 Redux-saga에 대해 살펴보자. Redux-saga는 generator 기반으로 동작하는 middleware이다. 따라서 사용을 위해서는 generator에 대한 기본적인 이해가 선행되어야 한다.

💡 generator가 생소하신 분들은 여기를 읽고 오시는 것을 추천드립니다!

Redux-saga는 제너레이터 함수 문법을 기반으로 비동기 작업을 관리한다. Redux-saga는 우리가 dispatch하는 action을 모니터링해서 그에 따라 필요한 작업을 따로 수행하는 방식으로 동작한다.

Redux-saga를 설명하기 전에, 기본적으로 알아야 할 주요 함수들을 먼저 살펴보자. 'redux-saga/effects' 에는 다양한 util 함수들이 들어있다. 이 중에서 주로 사용되는 delay, call, put, takeEvery, takeLatest에 대해 살펴보자.

delay

파라미터로 넘긴 시간 만큼 delay 되었다가 다시 실행된다.

put

put을 이용하면 새로운 action을 dispatch 할 수 있다.

call

put이 action을 dispatch 해 준다면, call은 함수를 실행할 수 있다. 첫 번째 파라미터는 함수, 나머지 파라미터는 해당 함수에 넣을 인자이다.

takeEvery

특정 action 타입에 대하여 dispatch되는 모든 action들을 처리한다.

takeLatest

특정 action 타입에 대하여 dispatch된 가장 마지막 action만을 처리하는 함수이다. 즉 기존에 진행 중이던 작업이 있다면 취소 처리하고 가장 마지막으로 실행된 작업만 수행한다.




그러면 위 유틸 함수들이 실제 코드 상에서는 어떻게 사용 되는지 알아보자. 코드는 공식문서에 있는 코드를 참조했다.

class UserComponent extends React.Component {
  ...
  onSomeButtonClicked() {
    const { userId, dispatch } = this.props
    dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
  }
  ...
}

UserComponent는 user가 버튼을 누르게 되면 USER_FETCH_REQUESTED action을 dispatch하는 간단한 컴포넌트이다. 이후 dispatch된 action을 saga가 catch하게 된다.

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...' // API가 있다고 가정

function* fetchUser(action) {
   try {
      // api 호출
      const user = yield call(Api.fetchUser, action.payload.userId);
  	  // action dispatch
      yield put({type: "USER_FETCH_SUCCEEDED", user: user}); 
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

// USER_FETCH_REQUESTED action dispatch 되면 실행
function* mySaga() {
  // 모든 요청에 대해 fetchUser 호출
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

mySaga 함수에서 takeEvery 함수를 호출했으므로, 모든 요청에 대해 fetchUser 함수를 호출하게 된다.

fetchUser 함수 내부에서는 api를 호출하고 put api를 통해 성공시 USER_FETCH_SUCCEEDED action을, 실패시 USER_FETCH_FAILED action을 dispatch 한다.

정리

지금까지 redux에서 비동기처리를 하기 위해 사용되는 두가지 middleware인 thunk와 saga에 대해 살펴보았다.

두 middleware의 가장 큰 차이점은 redux-thunk는 action뿐만 아니라 함수를 dispatch 할 수 있게 해주는 미들웨어라면, redux-saga는 특정 action을 모니터링하고 있다가 action 발생 시 특정 작업을 하는 방식으로 진행된다는 점이다.


주된 차이점을 마지막으로 정리해 보면 다음과 같다.

  • redux-thunk는 함수를 만들어서 해당 함수에서 비동기 작업을 하고 필요한 시점에 해당 action을 dispatch한다.

  • redux-saga는 특정 action을 모니터링 하도록 하고, 그 action 발생시 이에 따라 제너레이터 함수를 실행하여 비동기 작업을 처리 후 action을 dispatch 한다.



개인적 의견!

프로젝트를 진행하며 thunk와 saga 둘 다 사용해 보았는데, redux saga를 이용하면 thunk에 비해 조금 더 깔끔한 코드를 작성 할 수 있다고 느꼈다.

예를들면, 특정 action dispatch시 두개 이상의 api를 호출해야 하는 경우, saga의 call api를 이용하면 thunk를 사용할 때 보다 코드를 훨씬 더 간결하게 작성할 수 있었다.

redux-thunk에 경우 saga에 비해 다소 제약적이라고 느껴졌지만 redux toolkit에 기본적으로 thunk가 내장되어 있으므로 간결하게 사용할 수 있었고,

saga의 경우 generator 기반으로 동작하기 때문에 팀원 모두가 generator에 대해 이해하고 있어야 수월하게 사용할 수 있다는 단점이 있었다.

둘 다 장단점이 있으므로 프로젝트에 따라 적절히 선택해서 사용 하는게 좋을 것 같다. 😁

2개의 댓글

comment-user-thumbnail
2021년 6월 1일

잘 보고갑니다.

1개의 답글