지금부터는 실제로 Redux에서 Saga를 구현할 수 있게 해주는 redux-saga
라이브러리에 대해서 살펴보도록 하겠습니다.
위 화면은 Redux-Saga 라이브러리의 공식 홈페이지입니다.
가운데에 Redux-Saga의 정의가 이렇게 나와 있습니다.
“An intuitive Redux side effect manager.”
우리말로 하면 직관적인 Redux side effect 관리자라는 뜻이죠.
즉, Redux-Saga는 Redux에서 side effect를 편하고 직관적으로 사용할 수 있게 해주는 라이브러리라고 이해하면 됩니다.
그래서 Redux-Saga의 로고를 보면 이렇게 Redux로고의 옆쪽에 고리를 끼워서 마치 Side Effect가 실행되는 것 같은 모양을 하고 있습니다.
로고에도 깊은 의미가 담겨있다고 볼 수 있겠네요.
그리고 이 화면은 Redux-Saga의 공식 GitHub respository입니다.
Star가 2만2천개가 넘는 것을 볼 수 있습니다.
그만큼 많은 개발자들이 사용하고 있다는 것이죠.
지금부터는 redux-saga
의 특징들을 하나씩 살펴보도록 하겠습니다.
먼저 첫 번째 특징은 Declarative Effects입니다.
우리말로 하면 선언적 Effects라는 뜻인데, redux-saga
에서는 Effect라는 것을 선언하는 형태로 Side Effect를 사용한다는 뜻입니다.
그렇다면 redux-saga
에서 말하는 Effect는 뭘까요?
여기서 말하는 Effect는 Side Effect의 Effect와는 조금 다른 의미를 갖고 있습니다.redux-saga
에서의 Effect는 Generator로부터 yield된 JavaScript 객체를 의미합니다.
이 객체는 redux-saga
middleware에서 필요로 하는 정보들을 포함하고 있습니다.
그리고 이러한 Effect들을 생성하기 위해서는, redux-saga
의 effects 패키지에 포함된 함수들을 사용하면 됩니다.
그리고 이렇게 Effect를 생성해주는 함수들을 Effect Creator라고 부릅니다.
import {
take,
takeEvery,
takeLatest,
takeLeading,
put,
call,
select,
} from 'redux-saga/effects';
위 코드에 나와 있는 함수들은 redux-saga
에서 자주 사용되는 대표적인 Effect Creator들을 나타낸 것입니다.
보시면 take
, takeEvery
, takeLatest
, takeLeading
, put
, call
, select가
있는데, 뭔가 굉장히 어렵게 느껴지죠?
바로 뒷 장에서 하나씩 살펴보도록 할 예정입니다.
import { takeLeading } from 'redux-saga/effects';
import apiClient from '../api/client';
funtion* fetchPosts() {
const posts = yield apiClient.fetch('/api/posts');
}
export funtion* fetchPostsSaga() {
yield takeLeading('FETCH_POSTS_REQUESTED', fetchPosts);
}
이러한 Effect Creator는 JavaScript 객체 형태로 된 Effect를 생성해주는 역할만 할 뿐, 실제로 어떤 작업을 수행하지는 않습니다.
실제 Effect를 실행하는 것은 redux-saga
middleware에서 하게 되는데, 이 때 Effect 객체에 들어있는 정보를 토대로 실행을 하게 됩니다.
아래 그림은 Saga가 실행되는 것을 그림으로 나타낸 것입니다.
이 그림처럼 Saga의 흐름에는 다양한 Effect들이 존재하고 이러한 Effect들이 순서대로 처리됩니다.
그리고 이러한 Effect를 생성하는 역할을 하는 것이 바로 Effect Creator입니다.
지금부터는 redux-saga
의 주요 Effect Creator들을 하나씩 살펴보도록 하겠습니다.
take(pattern)
takeEvery(pattern, saga, ...args)
pattern
과 일치하는 Action에 대해 saga를 생성takeLatest(pattern, saga, ...args)
takeEvery()
와 동일하지만, 이전에 시작된 이전 saga가 실행 중인 경우 자동으로 취소takeLeading(pattern, saga, ...args)
takeEvery()
와 동일하지만, 생성된 saga가 완료될 때까지 새로운 saga 생성을 차단put(action)
call(fn, ...args)
fn
)를 호출을 하는 Effect를 생성select(selector, ...args)
selector
에 해당하는 State를 선택하는 Effect를 생성먼저 take
로 시작하는 Effect Creator들이 나오는데, 가장 첫 번째 Effect Creator는 take()
입니다.take()
함수는 Effect 객체를 생성하는 역할을 합니다.
여기서 pattern
은 실행 할 Action을 걸러내기 위한 패턴이라고 보시면 됩니다.
우리가 알고 있는 대표적인 pattern
으로는 Action Type이 있습니다.
pattern을 생략하거나 *
일 경우 모든 Action을 실행하고, 함수일 경우 pattern(action)
의 호출 결과가 true
인 Action들만 실행하며, 문자열일 경우 action.type
이 pattern
과 일치하는 Action들만 실행하고, 배열일 경우 배열에 있는 각 pattern
에 위의 규칙들을 적용하여 해당되는 Action들만 실행하게 됩니다.
takeEvery()
함수는 Redux Store에 Dispatch된 Action중, pattern
과 일치하는 모든 Action에 대해 saga를 생성합니다.
만약 pattern
과 일치하는 Action이 여러번 Dispatch된다면, saga가 동시에 여러개 생성될 수 있습니다.
그리고 takeLatest()
함수는 takeEvery()
와 동일하지만, 이전에 시작된 이전 saga가 실행 중인 경우 자동으로 취소됩니다.
이름에 포함된 Latest라는 단어의 의미 그대로 가장 최근의 saga만 수행되도록 하는 것이죠.
그리고 takeLeading()
함수도 역시 takeEvery()
와 동일하지만, 생성된 saga가 완료될 때까지 새로운 saga 생성을 차단합니다.
이름에 포함된 Leading이라는 단어의 의미 그대로 Leading하는. 즉, 가장 먼저 시작된 saga만 수행되도록 하는 것입니다.
다음으로 나오는 redux-saga
의 주요 Effect Creator는 put
, call
, select
입니다.
먼저 put()
함수는 Action 객체를 파라미터로 받아서 Effect를 생성하고, 이를 saga에 집어넣는 함수입니다.
saga 작업 대기열에 다른 Action이 실행중일 수 있기 때문에, put()
함수로 인해 생성된 Effect는 곧바로 실행되지 않을 수 있습니다.
다음은 call()
함수입니다.call()
함수는 이름 그대로 함수를 호출을 하는 역할을 합니다.
이때 호출하는 대상은 바로 첫 번째 파라미터로 받는 함수입니다.
이 함수로는 Generator function이 올 수도 있고,
아니면 Promise
나 다른 값을 리턴하는 일반적인 함수가 올 수도 있습니다.
그리고 함수를 호출 할 때, 두 번째부터 나오는 복수 개의 파라미터들을 호출하는 함수의 파라미터로 넣어줍니다.
그리고 마지막으로 나오는 select()
함수는 Redux Store에서 selector
에 해당하는 State를 선택하는 Effect를 생성해줍니다.
이름 그대로 State를 선택하기 위해서 사용하는 Effect Creator라고 보면 됩니다.
import { takeLeading } from 'redux-saga/effects';
import apiClient from '../api/client';
function* fetchPosts() {
const posts = yield apiClient.fetch('/api/posts');
}
export function* fetchPostsSaga() {
yield takeLeading('FETCH_POSTS_REQUESTED', fetchPosts);
}
이 코드는 실제로 Effect Creator를 사용하는 예시 코드입니다.
여기에서는 takeLeading
을 사용하고 있는 것을 볼 수 있습니다.
먼저 FETCH_POSTS_REQUESTED
type을 가진 Action이 Dispatch되면, takeLeading()
Effect Creator가 Effect를 만들어서 Saga작업 대기열에 집어넣게 됩니다.
이렇게 생성된 Effect에는 fetchPosts
Generator Function을 사용해서 만들어진 Generator가 포함되어 있습니다.
그리고 이 Generator를 이용해서 서버로부터 posts
를 받아오게 됩니다.
이처럼 redux-saga
에서는 그때그때 필요한 Effect들을 선언해서 사용하기 때문에, Declarative Effects라는 특징을 갖고있다고 하는 것입니다.
다음 두 번째 redux-saga
의 특징은 Dispatching Actions입니다.
Saga를 사용하다보면 Generator Function 내에서 Action을 Dispatch하고 싶을 경우가 있습니다.
import { call } from 'redux-saga/effects';
import apiClient from '../api/client';
function* fetchPosts(dispatch) {
const posts = yield call(apiClient.fetch, '/api/posts');
dispatch({
type: 'FETCH_POSTS_SUCCEEDED',
payload: {
posts
}
});
}
그런 경우에 위 코드처럼 dispatch를 generator function의 파라미터로 넘겨서 Action을 Dispatch할 수 있습니다.
하지만 이렇게 하면 fetchPosts
saga가 dispatch 함수에 의존하게 되므로 테스트하기 어려운 구조가 됩니다.
그래서 dispatch를 넘기는 방법대신 아래와 같이 Effect Creator를 사용하는 방법을 사용하는 것이 좋습니다.
import { call, put } from 'redux-saga/effects';
import apiClient from '../api/client';
function* fetchPosts() {
const posts = yield call(apiClient.fetch, '/api/posts');
// Action을 Dispatch하기 위한 Effect를 생성하고 yield
yield put({
type: 'FETCH_POSTS_SUCCEEDED',
payload: {
posts
}
});
}
Action을 Dispatch하기 위한 목적의 Effect를 만들고, 그것을 saga에 넣어서 실제로 Action을 Dispatch하는 것은 saga middleware에서 처리하도록 하는 것이죠.
그리고 이런 형태로 구현하게 되면 saga를 테스트하기가 쉬워집니다.
결론적으로 redux-saga
에서는 Action을 Dispatch하기 위해서 put()
함수를 사용하면 됩니다.
지금까지 redux-saga
의 특징과 API에 대해서 간단하게 살펴보았습니다.
사실, redux-saga
의 모든 기능과 API에 대해서 배우려면, redux-saga
에 대해서만 별도의 강의로 제작해야 할 정도로 내용들이 많습니다.
이 문서는 Redux에 입문하는 분들을 위한 문서이기 때문에, redux-saga
의 기초적인 API와 사용법만을 다뤘습니다.
혹시 추가로 더 자세한 내용이 궁금한 분들은 아래 링크에 접속해서 다양한 API들을 살펴보기 바랍니다.
마지막 업데이트: 2023년 07월 14일 00시 00분
이 문서의 저작권은 이인제(소플)에 있습니다. 무단 전재와 무단 복제를 금합니다.