Redux-Sage

이선민·2022년 1월 10일
0

Redux-Saga란?

redux-saga는 리덕스의 미들웨어

Redux-thunk와 Redux-saga가 대표적인데, Redux-thunk의 경우 saga에 대비해 쉬운 코드와 높은 생산성이 있지만, 태생적인 한계 때문에 복잡한 비동기 처리가 어렵다.

Saga의 경우 thunk에 비해서 더 복잡한 비동기 처리를 할 수 있다는 점을 들 수 있다.

redux-saga 설치하기

$ npm install redux-saga
$ yarn add redux-saga

Redux-Saga 설정

configureStore.js 에서 redux-saga 임포트하고 설정해주기

// configureStore.js
import { createWrapper } from 'next-redux-wrapper';
import { createStore, applyMiddleware, compose } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSageMiddleware from 'redux-saga';

import reducer from './reducers';
import rootSaga from './sagas';

const configureStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const middlewares = [sagaMiddleware, loggerMiddleware];
  const enhancer =
    process.env.NODE_ENV === 'production'
      ? compose(applyMiddleware(...middlewares))
      : composeWithDevTools(applyMiddleware(...middlewares));
  const store = createStore(reducer, enhancer);
  store.sagaTask = sagaMiddleware.run(rootSaga);
  return store;
};

const wrapper = createWrapper(configureStore, {
  debug: process.env.NODE_ENV === 'development',
});

export default wrapper;

generator 이해하기

const gen = function* () {
  console.log(1)
  yield;
  console.log(2);
  yield;
  console.log(3);
  yield 4;
}

const generator = gen();
generator.next(); // 1, {value: undefined, done: false}
generator.next(); // 2, {value: undefined, done: false}
generator.next(); // 3, {value: 4, done: false}
generator.next(); // {value: undefined, done: done}

// 여기서 generator는 yield가 있는 곳에서 멈추는 것을 알 수 있다.(중단점이 있는 함수)
// 자바스크립트 함수의 특성을 보면 함수를 실행하면 최상단에서부터 끝까지 실행된다.
// yield 뒤에 숫자나 값을 넣어주면 그 값이 value로 리턴된다.
let i = 0;
const gen = function* () {
  // while (true) -> 무한 반복이 걸려서 프로그램이 멈추게 된다.
  // saga에서는 다르게 동작한다. (매번 중단됨)
  while(true) {
    yield i++;
  }
}

const generator = gen();
generator.next(); // {value: 1, done: false}
generator.next(); // {value: 2, done: false}
generator.next(); // {value: 3, done: false}
generator.next(); // {value: 4, done: false}
generator.next(); // {value: 5, done: false}
// 자바스크립트에서 무한의 개념을 표현하고 싶을 때 generator를 많이 사용한다.
// generator는 이벤트리스너처럼 활용이 가능하다.

redux-saga effects

put
dispatch로 생각하면 된다.

takeEvery
모든 액션에 대하여 동작한다. (비동기)

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

// takeLatest -> 실수로 클릭 2번했을 때 마지막 요청만 보내준다.
// ✅ 서버에는 2번의 요청이 간 것으로 인식하고 2번의 응답을 보내주는데 프론트에서 요청은 취소를 못하고 응답 한번을 취소하게 된다.
function* watchLogIn() {
  yield takeLatest('LOG_IN_REQUEST', logIn);
}

take
해당 액션이 dispatch 되면 제너레이터를 next 하는 이펙트이다.

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

// take의 단점은 1회용이다. (한 번 로그인하면 사라짐)
function* watchLogIn() {
  yield take('LOG_IN_REQUEST', logIn);
}

// while-take는 동기적으로 동작하지만 takeEvery는 비동기로 동작한다.
// 해결 방법 -> while(true), takeEvery, takeLatest
function* watchLogIn() {
  while (true) {
    yield take('LOG_IN_REQUEST', logIn);
  }
}

function* watchLogIn() {
  yield takeEvery('LOG_IN_REQUEST', logIn);
}

function* watchLogIn() {
  yield takeLatest('LOG_IN_REQUEST', logIn);
}

fork, call
fork와 call 모두 함수를 실행시켜주는 이펙트이다.
→ fork는 비동기 실행을 한다.
→ call은 동기 실행을 한다. 따라서 순서대로 함수를 실행해야하는 API 요청 같은 곳에 쓰인다.

throttle
마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것(특수한 경우에만 사용. 보통 takeLatest사용)

// 2초 동안은 게시물 작성 요청은 1번만 가능
function* watchAddPost() {
  yield throttle('ADD_POST_REQUEST', addPost, 2000);
}

delay
정해놓은 시간동안 비동기적인 효과를 줌


redux-saga 코드 흐름

import { all, fork, call, put, takeLatest } from 'redux-saga/effects';
import axios from 'axios';

function logInAPI(data) {
  // 서버 요청
  return axios.post('api/login', data);
}

function* logIn(action) {
  try {
    const result = yield call(logInAPI, action.data); // logInAPI를 실행하고 서버에 요청한 결과값을 받을 수 있다.
    yield put({
      type: 'LOG_IN_SUCCESS',
      data: result.data,
    });
  } catch (err) {
    // 요청이 실패한 경우
    console.error(err);
    yield put({
      type: 'LOG_IN_FAILURE',
      error: err.response.data,
    });
  }
}

function logOutAPI() {
  return axios.post('/api/logout');
}

function* logOut() {
  try {
    // const result = yield call(logOutAPI);
    yield put({
      type: 'LOG_OUT_SUCCESS',
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: 'LOG_OUT_FAILURE',
      error: err.response.data,
    });
  }
}

function* watchLogIn() {
  // login action이 실행되면 logIn generator function을 실행한다.
  yield takeLatest('LOG_IN_REQUEST', logIn);
}

function* watchLogOut() {
  yield takeLatest('LOG_OUT_REQUEST', logOut);
}

// rootSaga를 만들고 그 안에 만들고 싶은 비동기 액션을 넣어준다.
export default function* rootSaga() {
  // all은 배열을 받는데 배열 안에 있는 것들을 한 번에 실행시켜준다.
  yield all([fork(watchLogIn), fork(watchLogOut)]);
}

0개의 댓글