처음 만난 리덕스 (Redux) 문서


12.3 redux-saga

지금부터는 실제로 Redux에서 Saga를 구현할 수 있게 해주는 redux-saga 라이브러리에 대해서 살펴보도록 하겠습니다.

redux-saga website

위 화면은 Redux-Saga 라이브러리의 공식 홈페이지입니다.
가운데에 Redux-Saga의 정의가 이렇게 나와 있습니다.

“An intuitive Redux side effect manager.”

우리말로 하면 직관적인 Redux side effect 관리자라는 뜻이죠.
즉, Redux-Saga는 Redux에서 side effect를 편하고 직관적으로 사용할 수 있게 해주는 라이브러리라고 이해하면 됩니다.

redux-saga logo

그래서 Redux-Saga의 로고를 보면 이렇게 Redux로고의 옆쪽에 고리를 끼워서 마치 Side Effect가 실행되는 것 같은 모양을 하고 있습니다.
로고에도 깊은 의미가 담겨있다고 볼 수 있겠네요.

redux-saga GitHub

그리고 이 화면은 Redux-Saga의 공식 GitHub respository입니다.
Star가 2만2천개가 넘는 것을 볼 수 있습니다.
그만큼 많은 개발자들이 사용하고 있다는 것이죠.

12.3.1 Declarative Effects

지금부터는 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 Flow

이 그림처럼 Saga의 흐름에는 다양한 Effect들이 존재하고 이러한 Effect들이 순서대로 처리됩니다.
그리고 이러한 Effect를 생성하는 역할을 하는 것이 바로 Effect Creator입니다.

지금부터는 redux-saga의 주요 Effect Creator들을 하나씩 살펴보도록 하겠습니다.

  • take(pattern)
    • Effect를 생성
  • takeEvery(pattern, saga, ...args)​
    • Redux Store에 Dispatch된 Action중, pattern과 일치하는 Action에 대해 saga를 생성
  • takeLatest(pattern, saga, ...args)
    • takeEvery()와 동일하지만, 이전에 시작된 이전 saga가 실행 중인 경우 자동으로 취소
  • takeLeading(pattern, saga, ...args)​
    • takeEvery()와 동일하지만, 생성된 saga가 완료될 때까지 새로운 saga 생성을 차단
  • put(action)​
    • Action 객체를 파라미터로 받아서 Effect를 생성하고 saga에 집어넣는 함수
    • 다른 Action이 실행중일 수 있기 때문에 곧바로 실행되지 않을 수 있음.
  • call(fn, ...args)​
    • 첫 번째 파라미터인 함수(fn)를 호출을 하는 Effect를 생성
    • 두 번째부터 나오는 복수 개의 파라미터들을 넣어서 호출함.
  • select(selector, ...args)​
    • Redux Store에서 selector에 해당하는 State를 선택하는 Effect를 생성

먼저 take로 시작하는 Effect Creator들이 나오는데, 가장 첫 번째 Effect Creator는 take() 입니다.
take() 함수는 Effect 객체를 생성하는 역할을 합니다.
여기서 pattern은 실행 할 Action을 걸러내기 위한 패턴이라고 보시면 됩니다.
우리가 알고 있는 대표적인 pattern으로는 Action Type이 있습니다.
pattern을 생략하거나 *일 경우 모든 Action을 실행하고, 함수일 경우 pattern(action)의 호출 결과가 true인 Action들만 실행하며, 문자열일 경우 action.typepattern과 일치하는 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라는 특징을 갖고있다고 하는 것입니다.

12.3.2. Dispatching Actions

다음 두 번째 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들을 살펴보기 바랍니다.

https://redux-saga.js.org/docs/api


마지막 업데이트: 2023년 07월 14일 00시 00분

이 문서의 저작권은 이인제(소플)에 있습니다. 무단 전재와 무단 복제를 금합니다.