Redux - Saga의 제너레이터 이해하기, 이펙트

BigbrotherShin·2020년 3월 13일
5

Frontend

목록 보기
7/31
post-thumbnail

/sagas/user.js

import { all, fork, takeLatest, call, put, take, delay } from 'redux-saga/effects';
import { HELLO_SAGA } from '../reducers/user';

function* helloSaga() {
  console.log('before saga');
  yield take(HELLO_SAGA); // HELLO_SAGA 액션이 들어오면 함수 재진행
  console.log('hello saga'); // HELLO_SAGA 액션 이후 -> 'hello saga'
}

export default function* userSaga() {
  yield all([fork(watchLogin), helloSaga()]);
}
  • take: 해당 액션이 dispatch 되면 제너레이터를 next 하는 이펙트
    위의 예제에서 take는 yield에서 진행이 멈추어 있는 함수를 HELLO_SAGA 액션이 들어오면 재개시킴 (next를 실행)
    주의할 점은 take 이후의 코드를 실행시킨 다는 것이다.

Generator의 무한반복문 활용

위의 예제의 helloSaga 제너레이터는 HELLO_SAGA 액션이 한 번 들어오면 함수가 끝이난다.
HELLO_SAGA 액션이 들어올 때 마다 특정 동작을 하게 하기 위해서는 while문을 통한 제너레이터의 무한반복문을 활용할 수 있다.

function* helloSaga() {
  console.log('before saga');
  while (true) {
    yield take(HELLO_SAGA);
    console.log('hello saga');
  }
}

dispatch(HELLO_SAGA) // -> 'hello saga'
dispatch(HELLO_SAGA) // -> 'hello saga'
dispatch(HELLO_SAGA) // -> 'hello saga'
.
.
.

이벤트리스너를 동기적으로 표현했다고 생각하면 이해하기 쉽다.

예제

LOG_IN 액션 이후 2초 뒤 LOG_IN_SUCCESS 액션 dispatch

import { all, put, take, delay } from 'redux-saga/effects';
import { LOG_IN, LOG_IN_SUCCESS } from '../reducers/user';


function* watchLogin() {
  while (true) {
    yield take(LOG_IN); // LOG_IN 이 dispatch 되면, take로 인해 함수 다음 단계 실행
    yield delay(2000); // delay 이펙트: 일정 시간 후 다음 함수 단계 실행.
    yield put({  // 그렇기 때문에 put 실행됨
      type: LOG_IN_SUCCESS,
    });
  }
}


export default function* userSaga() {
  yield all([watchLogin(), watchSignUp()]);
}

put은 dispatch로 생각하면 된다.

Redux-Saga의 이펙트

put

put은 dispatch로 생각하면 된다.

takeEvery

takeEvery는 모든 액션에 대하여 동작한다.

일단은 takeEvery를 사용하지 않고, take만 사용하여 예제를 작성해보자.

import { take } from 'react-saga/effects';

function* watchHello() {
  while (true) {
    yield take(HELLO_SAGA);
    console.log(1);
    console.log(2);
    console.log(3);
    console.log(4);
  }
}

위의 예제는 redux-saga의 이펙트인 takeEvery를 사용하여 다음과 같이 작성할 수 있다.

import { take, takeEvery } from 'react-saga/effects';

function* watchHello() {
  yield takeEvery(HELLO_SAGA, function*() {
    // 어떤 동작을 하는 지 코드 작성
    console.log(1);
    console.log(2);
    console.log(3);
    console.log(4);
  });
}

takeEvery 또는 takeLatesttake와는 달리 특정 동작을 명확하게 지정하여 코드를 작성할 수 있기 때문에 코드 가독성 측면에서 더 좋다.

takeLatest

takeEvery가 같은 종류의 액션이라도 모든 액션에 대하여 주어진 동작을 실행하는 반면에,

takeLatest는 같은 종류의 액션이 여러 번 요청된다면 가장 마지막 액션에 대해서만 동작을 실행한다. 즉 이전 액션 요청이 끝나지 않았음에도 불구하고 같은 종류의 액션이 여러 번 요청된다면 이전 요청을 취소한다. 웹페이지에서 특정 버튼을 여러 번 클릭하는 경우에 사용한다고 생각하면 된다.

import { take, takeLatest } from 'react-saga/effects';

function* watchHello() {
  yield takeLatest(HELLO_SAGA, function*() {
    console.log(1);
    console.log(2);
    console.log(3);
    console.log(4);
  });
}

fork, call

call(fn, ...args)

Creates an Effect description that instructs the middleware to call the function fn with args as arguments.

  • fn: Function - A Generator function, or normal function which either returns a Promise as result, or any other value.
  • args: Array - An array of values to be passed as arguments to fn
    Redux-Saga API 레퍼런스 · GitBook

forkcall 모두 함수를 실행시켜주는 이펙트이다. 두 가지는 다음과 같은 차이가 있다.

fork는 비동기 실행을 한다.
call은 동기 실행을 한다. 따라서 순서대로 함수를 실행해야하는 API 요청 같은 곳에 쓰인다.

import { all, fork, call, put } from 'redux-saga/effects';
import { LOG_IN, LOG_IN_SUCCESS, LOG_IN_FAILURE } from '../reducers/user';

function loginAPI() {
  // 서버에 요청을 보내는 부분
}

function* login() {
  try {
    yield fork(logger); // fork는 비동기 요청. logger는 내 기록을 로깅하는 함수. 10초 걸림
    yield call(loginAPI); // call은 동기 요청
    yield put({
      //put은 dispatch와 동일
      type: LOG_IN_SUCCESS,
    });
  } catch (e) {
    console.error(e); // loginAPI 실패
    yield put({
      type: LOG_IN_FAILURE,
    });
  }
}

export default function* userSaga() {
  yield all([fork(watchLogin)]); // yield all([watchLogin()]) 과 같음
}
profile
JavaScript, Node.js 그리고 React를 배우는

0개의 댓글