COCO World

[React/리액트] Redux Saga에 대해 알아보자. 본문

Front-End/React

[React/리액트] Redux Saga에 대해 알아보자.

코코월드주인장 2023. 8. 4. 17:16

1. 리덕스 사가(Redux Saga) 

 

Redux-Thunk는 함수를 디스패치 할 수 있게 해주는 미들웨어였다면, Redux-Saga의 경우엔, Action을 모니터링하고 있다가, 특정 액션이 발생하면 이에 따라 특정 작업을 수행하는 방식으로 사용한다. 야기서 특정 작업이란, 특정 자바스크립트를 실행하는 것 일수도 있고, 다른 액션을 Dispatch하는 것 일수도 있고, 현재 상태를 불러오는 것 일수도 있다.

 

Redux-Thunk 다음으로 가장 사용되고 있는 라이브러리로써 주로 애플리케이션에서 비동기적으로 API를 호출하여 데이터를 가져오는 일과 같은 부수 효과(Side Effect)를 쉽게 처리하기 위해 사용하는 라이브러리이다.위의 특정 작업에 API의 비동기 호출 행위가 포함되며, 때에 따라 기존 요청을 취소 처리해야 한다던가 여러 개의 API를 순차적으로 호출해야 하는 등의 좀 더 까다로운 비동기 작업을 다룰 때 유용하다.

 

리덕스 사가(Redux Saga)는 애플리케이션에서 전적으로 부수 효과만을 담당하여 처리하는데 비동기 함수 호출 결과 데이터를 통해 성공, 실패 여부를 판단하고 상태를 업데이트 시키는 등의 작업(Task)을 제어할 수 있으며, 스토어에 접근하거나 특정 액션(Action)을 디스패치(Dispatch)하여 다른 사가함수를 실행시킬 수 있다.

 

리덕스 사가(Redux Saga)를 사용하기 위해선 제너레이터(Generator)라는 ES6 기능을 사용한다. 제너레이터를 사용하게되면 비동기의 흐름을 표준 동기 코드처럼 보이게 하여 비동기 흐름을 쉽게 읽고 쓰고 테스트할 수 있어 복잡한 워크플로우를 관리하는데 효과적이다.

 


2. 제너레이터(Generator) 문법 알아보기

https://ko.javascript.info/generators

 

제너레이터

 

ko.javascript.info

 


 

3. 리덕스 사가(Redux Saga) API

import { createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'

import { combineReducers } from "redux";
import todos from "../modules/todos.js";

// 스토어에 적용시킬 action 모듈
const rootReducer = combineReducers({
    todo
});

// saga 미들웨어 생성
const sagaMiddleware = createSagaMiddleware();

// 리덕스의 가장 핵심인 스토어를 만드는 메소드
const store = createStore(
    rootReducer,
    applyMiddleware(sagaMiddleware)
);

// 그리고 saga를 실행
sagaMiddle.run(mySaga);

 

1) createSagaMiddleware

: 리덕스 사가(Redux Saga)에서 제공하는 createSagaMiddleware API를 사용하면 사가 미들웨어(SagaMiddleware)를 생성할 수 있다. 그리고 생성된 사가 미들웨어는 리덕스(Redux)에서 제공하는 applyMiddleware API를 호출할 때 인자로 넘겨 리덕스 미들웨어(Redux Middlewre)로 추가할 수 있다.

 

2) middleware.run(saga, ...args)

: 사가 미들웨어의 run 메서드를 통해 사가(Saga)를 실행할 수 있다. 사가가 여러 개 존재하는 경우 진입점(Entry Point)에 해당하는 루트 사가(Root Saga)를 만들고 이 루트 사가를 실행시켜줄 수 있다.

 

사가(Saga)가 실행되면 해당 제너레이터(Genertor) 함수를 호출하여 반복 가능한 제너레이터를 획득하게 되는데 해당 제너레이터의 next 메서드를 통해 이펙트(Effect) 타입을 확인하고 해당 이펙트에 대해 지시된 동작을 수행하는 작업을 반복하게 됩니다.

 


 

4. 리덕스 사가(Redux Saga) 이펙트(Effect) 함수

 

앞서 언급한 이펙트(Effect)라고 하는 것은 어떤 기능을 수행하기 위해 주어진 함수와 인자들을 담은 명령 객체를 미들웨어에게 전달하는데 이러한 명령 객체를 말한다. 이펙트를 전달받은 미들웨어는 yield된 이펙트들을 확인하며 정확한 명령이 포함되었는지 검사하고, 이펙트 타입에 따라 어떻게 이펙트를 수행할지 결정한다.

 

이펙트 설명을 위한 예제

import { put, takeEvery, delay } from 'redux-sga/effects'

export function* buyCake() {
    yield delay(1000)
    yield put({ type : 'BUYCAKE' });
}

export function* watchBuyCakeAsync() {
    yield takeEvery('BUYCAKE_ASYNC', buyCake)
}

 

Saga는 buyCake의 함수 액션(Action)을 모니터링하고 있다가 delay와 put이라는 이펙트(Effect)를 yield하고 자바스크립트 객체를 반환한다. 순서상으로는,

buyCake 액션이 dispatch되고, delay 메서드를 통해 1초가 지나면 put 메서드를 통해 BUYCAKE액션을 디스패치하게 되어 각각의 delay와 put의 이펙트가 진행된다. 

 

이러한 이펙트를 생성할 때는 redux-saga/effects 패키지에 있는 라이브러리들이 제공하는 함수들을 사용하며 주요하게 사용하는 이펙트 함수 타입은 다음과 같다.

 


 

5. 이펙트(Effect) 함수 타입 종류

 

  • fork(fn, ...args) 
    • 매개변수로 전달된 함수를 비동기적으로 실행한다. 비동기적으로 실행되기 대문에 블로킹(Blocking)이 발생하지 않는 새로운 맥락의 사가(Saga) 작업(Task)을 생성하게 된다
  • call(fn, ...args)
    • 매개변수로 전달된 동기 혹은 비동기 함수를 실행한다. 전달받은 함수가 비동기 함수인 경우 해당 함수가 수행될 때까지 기다렸다가 결과갓을 반환하므로 블로킹(Blocking)이 발생하게 된다.
  • put(action)
    • 액션(Action)을 디스패치(dispatch) 한다. 일반적으로 워커 사가(Worker Saga)에서 API 성공/실패 여부에 따라 상태를 반영하기 위해 리듀서에 액션을 디스패치 하는 용도로 많이 사용한다.
  • takeEvery(pattern, saga, ...args)
    • 액션(Action)이 디스패치(dispatch) 될 때마다 새로운 작업(Task) 분기(fork)를 한다. 작업이 동시에 중복으로 발생해도 문제가 없는 경우에 사용해야 한다.
function* takeEvery(pattern, saga, ...args) {
    const task = yield fork(function* () {
        while(true) {
            const action = yield take(pattern)
            yield fork(saga, ...args.concat(action))
        }
    })
    return task
}

 

  • takeLatest(pattern, saga, ...args)
    • 액션(Action)이 디스패치될 때 이전에 실행 중인 작업(Task)이 있다면 취소하고 새로운 작업을 분기(fork)합니다. 이전 API 요청을 무시하고 최신데이터를 받아올 수 있도록 한다.
function* takeLatest(pattern, saga, ...args){
    const task = yield fork(function * () {
        let lastTask
        while(true) {
            const action = yield take(pattern)
            if (lastTask)
                yield cancel(lastTask)
            
            lastTask = yield fork(saga, ...args.concat(action))
        }
    })
    return task
}

 

  • select(selector, ...args)
    • 리듀서(Reducer)에 있는 특정 상태를 리덕스 사가(Redux)로 가져와서 사용할 수 있다. 블로킹이 발생하여 Selector함수가 정보를 가져온 이후에 다음 작업(fork)을 수행할 수 있다
  • cancel(task)
    • yield fork()는 실행되는 작업에 대한 오브젝트를 반환한다. 이 오브젝트를 cancel 메서드의 인자로 넘기면 이미 비동기로 실행 중인 작업을 취소할 수 있다. 또한 작업을 취소한 경우라도 해당 워커 사가 내의 finally 구간 안에서는 취소된 작업에 대해 처리할 수 있다
  • 추가 설명
    • 모든 이펙트(Effect)는 반드시 yield 키워드와 함께 사용해야 한다. 리덕스 사가의 이펙트는 Blocking effect와 Non-Blocking effect로 구분된다. Blocking Effect는 처리가 완료될 때까지 기다리며 Non-Blocking Effect는 완료를 기다리지 않고 진행한다. 대표적인 Blocking Effect로는 Call이 있고, Non-blocking Effect에는 fork가 있다.