Redux-Saga란 무엇인가?
Redux-saga란, side effect를 관리하기 위한 middleware이다.
Side effect란, 애플리케이션의 부작용들(데이터 요청 등의 비동기 작업/브라우저 캐시 같은 순수하지 않은 것들)을 말한다.
middleware란, 액션이 dispatch 되어서 리듀서에서 이를 처리하기 전에 그 사이에서 중간자로써 지정된 특정 작업을 하는 것을 말한다.
Redux-Saga를 왜 사용하는가?
비동기 처리 등 순수하게 액션만을 전달하는 것 이상으로 발생하는 상황들(side effects)을 다룰 때, 이 미들웨어를 사용한다.
Redux-Thunk 대신에 Redux-Saga를 사용하는 이유는 무엇인가?
redux-thunk를 사용하면, 비동기 처리를 Action Creator(액션 생성자 함수)에서 작성하게 되어, 로직이 복잡해진다. 기본적으로 Redux에서 Action Creator에서는 액션을 객체형태로 생성하여 dispatch 하는데, redux-thunk에서는 액션을 함수형태로 생성하여 dispatch 하여, Action Creator에 비동기처리 코드나 관련된 로직이 들어가게 된다.
그런데, Redux-saga는 비동기 처리를 기술하는 전용의 방식인 task로 쓰여있다. 즉, 비동기 처리를 각 saga(task)에서 명령을 내리고, 동작을 미들웨어에서 처리한다. 그래서, redux에서의 action creator에서 기존과 그대로 액션을 객체형태로 생성하여 dispatch 할 수가 있고, saga에서의 코드도 더 간단해진다. 결국, Action Creator는 본래의 모습을 되찾아, action 객체를 생성하여 돌려주는 순수한 상태로 돌아가게 된다.
결론적으로, Redux-Saga를 사용하게 되면, 비동기 처리를 액션 생성자로부터 분리하여 작성하기 때문에, 가독성도 좋고 콜백 처리 등에 더 수월하다.
Redux-Saga Architecture
Redux-Saga Flow
위 Flow를 정리하면 다음과 같다.
- 어떤 비동기 액션이 dispatch 되면, Reducer에 먼저 도달한다. 정확하게는 액션이 Reducer로 지나가는 것을 본 후, Redux-Saga에 액션을 처리한다. Reducer는 순수 함수라는 규칙이 있기 때문에, 비동기 액션을 처리하지 못한다.
- 이 비동기 액션을 Redux-Saga에 있는 watcher saga가 보고 있다가, watcher saga에 등록되어 있는 task를 수행한다. 이 watcher saga의 역할은 어떤 비동기 액션이 dispatch 되면, 어떤 task를 수행하도록 등록하는 것이다. 이때, takeEvery라는 헬퍼 이펙트를 사용하는데, 이 이펙트는 여러개의 task를 동시에 시작할 수 있다. 즉, 1개 혹은 아직 종료되지 않은 task가 있더라도 새로운 task를 시작할 수 있다.
1 | // INCREMENT_ASYNC 액션이 Dispatch 되면 `incrementAsync`를 수행하도록 등록한다. |
incrementAsync
를 수행하는데, 만약 task가 2초 마다 +1씩 증가하는 함수라고 한다면, 다음과 같이 작성한다.
1 | function* incrementAsync(action) { |
- 위 task가 수행될 때, 먼저 첫번째 줄에 있는 delay(2000)이 yield 된다. delay는 설정된 시간 이후에 resolve를 하는 Promise 객체를 리턴하는 함수이다. 이 Promise가 미들웨어에 yield 될때, 미들웨어는 Promise가 끝날때까지 Saga를 일시정지 시킨다.(generator 함수의 특징) 즉, 이 부분은 동기적으로 동작한다.
- 2초후, Promise가 한번 resolve 되면, 미들웨어는 saga(task)를 다시 작동시키면서, 다음 yield까지 코드를 실행한다. 이런 방식으로 saga(task)에서 1개씩 yield되고, 그 yield 된 것이 미들웨어에 의해 동작이 완료되면, 그때 그 다음줄에 있는 객체가 yield된다.
- 어떤 객체를 yield할때, 앞에 오는 것들(put, call 등)을 이펙트라고 한다. 이펙트란, 미들웨어에 의해 수행되는 명령을 담고있는 간단한 자바스크립트 객체를 말한다. 미들웨어가 saga에 의해 yield 된 이펙트를 받을때, saga는 이펙트가 수행될때까지 정지되어 있다.
- saga에서 put을 통해 객체를 dispatch하면, 이 객체는 reducer로 가게된다.
- 두 saga를 모두 한번에 실행하게 해주기 위해, rootSaga를 이용해 사용한다.
1 | // 모든 Saga들을 한번에 시작하기 위한 단일 entry point 이다. |
Redux-Saga 사용방법
Redux-Saga를 사용하는 방법은 다음과 같다.
- saga 미들웨어 작성(sagas.js)
- 비동기 처리를 위한 task 작성(제너레이터 함수) - worker saga
- 각각의 어떤 비동기 액션을 처리하기 위해 watch 함수를 작성하고, 그 안에 takeEvery 이펙트 펠어 함수를 사용 - watcher saga
- 모든 saga들을 한번에 실행하기 entry point 작성 - rootSaga 작성
- rootSaga에 들어있는 두 saga가 호출된 결과의 배열을 yield 한다. 이것은 생성된 두 제너레이터가 병렬로 시작된다는 것을 의미.
- Saga 미들웨어를 Redux 스토어에 연결(main.js)
- applyMiddleware
- sagaMiddleware.run 등 작성
- 비동기 호출을 위한 이벤트 등록(Counter.js)
saga 미들웨어 작성(sagas.js)
1 | // sagas.js |
Saga 미들웨어를 Redux 스토어에 연결(main.js)
1 | // main.js |
비동기 호출을 위한 이벤트 등록(Counter.js)
1 | // Counter.js |
비동기 호출을 처리하기 위해 Redux와 연결해주는 컨테이너 컴포넌트(CounterContainer.js)
1 | function render() { |