Redux Thunk vs Redux Saga

meroriiDev·2023년 4월 5일
0
post-thumbnail

지난 글에서 redux-toolkit으로 세팅을 하면서 redux toolkit안에는 thunk가 기본적으로 포함이 되어있다는 것을 알게 되었다.

그런데, 왜 굳이 또 다른 툴을 설치해가며 saga를 추가하는건지 thunk와 saga의 차이점이 궁금해졌다.

Redux 미들웨어

리덕스는 Flux패턴을 가지는 상태관리 라이브러리로 액션을 디스패치하게 되면 reducer에서 그 해당 action의 정보를 바탕으로 store의 state값을 변경하게 되는데, 이때 미들웨어를 사용하게 되면 action이 store에서 state값을 바꾸기 전에 미리 가로채서 특정 작업들을 수행할 수 있게 하는 역할을 한다.

예를 들면 아래와 같은 역할들이 있다.

  • 특정 조건에 따라 액션 수행 여부를 판단
  • 액션을 콘솔에 출력하거나 서버쪽에 로깅
  • 액션이 디스패치 되었을 때 데이터를 수정/가공하여 리듀서에게 전달
  • 비동기적인 작업을 수행

이 중에서 Redux Thunk와 Redux Saga의 경우에는 예시 중 비동기처리에 주로 사용하게 된다.

Redux Thunk

redux는 기본적으로 action객체를 dispatch하여 특정 action을 reducer에 전달한다. 하지만 react-thunk를 사용하면 객체 뿐만 아니라, thunk 함수를 만들어 함수로도 dispatch할 수 있도록 한다.
thunk는 특정 작업을 나중에 할 수 있도록 미루기 위해 함수 형태로 감싼 것을 의미한다.

redux-toolkit과 함께 redux thunk를 사용하는 부분은 이전 글에 써두었으니 생략하고 saga로 바로 넘어가보려고 한다.


Redux Saga

redux saga 역시 비동기 작업을 위해 사용하는 미들웨어이지만 redux thunk와는 방식이 다르다.
redux thunk는 객체가 아닌 함수를 dispatch 할 수 있게 해주는 미들웨어라면,
redux saga는 actions을 모니터링 하고 있다가 특정 action이 발생했을 때 미리 정해진 로직에 따라 작업이 이루어지는 방식이다.

redux thunk에서는 제공되지 않는 saga에서만 제공되는 기능들이 있다.

  • 비동기 작업을 할 때 기존 요청을 취소할 수 있다.
  • 특정 액션이 발생했을 때 이에 따라 다른 액션이 디스패치 되도록 하거나, 자바스크립트 코드를 실행할 수 있다.
  • 웹 소켓을 사용하는 경우 Channel이라는 기능을 사용해 더욱 효율적으로 코드를 관리할 수 있다.
  • API 요청이 실패했을 때 재요청하는 작업이 가능하다.

generator(제너레이터)

saga는 ES6의 제너레이터함수를 사용해 함수를 구현한다.

제너레이터는 일반 함수와 다르게 특정 구간에 멈춰놓을 수도 있고, 원할 때 다시 실행시킬 수 있다. next()를 통해 다음 중단점까지 실행시킬 수 있다.
또한 이 때문에 한 함수에서 여러 결과값을 리턴하도록 할 수 있다.

function* generatorEx(){
	yield ~~~
}

제너레이터 함수를 선언할 때에는 function*이라는 키워드를 사용해야 한다.

yield키워드로 중단점을 만드는데 뒤에 오는 표현식은 제너레이터를 관찰하고 있는 호출자에게 반환하고 멈춘다. 일반적인 함수의 return 역할을 하는 키워드이다.

effect 함수 타입

redux saga에는 saga의 활용을 돕기 위한 다양한 effect들이 존재한다.
모든 effect는 반드시 yield키워드와 함께 사용해야 한다.

fork(fn, ...args)

매개변수로 전달된 함수를 비동기적으로 실행한다. 따라서 결과값을 기다려주지는 않는다.

call(fn, ...args)

매개변수로 전달된 함수를 동기적으로 실행한다. 전달받은 함수가 비동기 함수인 경우 해당 함수가 수행될때까지 기다렸다가 결과값을 반환한다.

put(action)

actions을 dispatch한다. 일반적으로 API 통신 성공/실패 여부에 따라 각 action을 dispatch하는 용도로 사용된다.

function* getComment() {
  try {
    const response = yield call(commentsAPI.getComment);
    yield put(commentActions.getCommentSuccess(response));
  } catch (error) {
    yield put(commentActions.getCommentError(error));
  }
}

takeEvery/takeLatest

인자로 들어온 action에 대해 특정 로직을 실행해준다.

yield takeLatest(commentActions.getComment, getComment);

takeLatest는 기존에 실행되던 작업이 있을 경우 취소하고, 가장 마지막으로 dispatch된 action을 처리하며,
takeEvery는 들어오는 모든 action을 처리한다.

all

여러 사가를 합쳐주는 역할을 한다. 파라미터로 배열을 받는데, 배열 내에 있는 것들을 모두 실행시킨다.

yield all([fork(watchGetComment)]);

Thunk vs Saga?

두가지 모두 사용해보니 확실히 thunk가 saga에 비해 코드의 양도 적고 이해하기가 쉬웠다. 하지만, thunk의 경우 초보자가 잘못 사용할 경우 복잡한 async 로직으로 인해 콜백 hell을 발생시킬 수 있으며, unit test가 어려운 구조로 되어있다는 단점이 있다.

saga를 사용하면 초기 러닝커브는 높지만 thunk에 비해 제공되는 기능들도 많고 큰 규모의 프로젝트의 경우 보다 더 안전할 수 있어 대부분 saga를 사용하여 프로젝트를 구성하는것을 선호하나보다.

recoil 짱짱
아무리 그래도 나는 recoil의 맛을 봐버린 이상 saga고 toolkit이고 딱히 매력적으로 보이지 않는다..! 그래도 시작했으니 마지막으로 toolkit+saga로 세팅하는 과정에 대해 글을 남기고 앞으로는 recoil와 jotai에 대해 더 공부해보려고 한다!

0개의 댓글