반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

redux-saga 본문

인프런, 유데미/Redux

redux-saga

5_hyun 2022. 12. 27. 23:59

configureStore.js

import { createWrapper } from "next-redux-wrapper";
import { applyMiddleware, compose, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";

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

const loggerMiddleware =
  ({ dispatch, getState }) =>
  (next) =>
  (action) => {
    console.log(action);
    return next(action);
  };

const configureStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const middlewares = [sagaMiddleware];
  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;

configureStore.js안의 파일에는 sagaMiddleware, store.sagaTask, rootSaga를 추가하여 만들어줬다.

Sagas

 

Sagas 폴더 구조를 알기 위해서는 generator를 먼저 알면 좋다.

 

-예시1

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: true}

gen은 function 뒤에 "*"을 붙인다. 그리고 generator에 gen을 넣어주고 next를 붙어야지만 실행할 수 있다.

한 번 실행할 때 마다 yeild에서 멈춰준다. done이 true가 될 때 까지 실행 가능하며 위 코드 주석처럼 실행이 된다.

이렇게 gen의 성질을 활용한 것이 saga이다.

 

-예시2

const gen = function* () {
  while (true) {
    yield "무한";
  }
};

const generator = gen();

generator.next();
// { value: "무한", done: false}
generator.next();
// { value: "무한", done: false}
generator.next();
// { value: "무한", done: false}
generator.next();
// { value: "무한", done: false}

원래 while(true)는 사용하면 안되지만 generator에선 yield 덕분에 사용이 가능하다.

let i = 0;

const gen = function* () {
  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}

이렇게 1씩 증가하는 코드를 만들 수도 있다.

next()를 활용하면 이벤트 리스너처럼 사용이 가능하다. 특정 이벤트가 발생했을 때 next()를 하면 된다.

sagas/index.js

sagas 폴더 안에 index.js를 만들었다.

import {
  all,
  fork,
  call,
  take,
  put,
  takeEvery,
  takeLatest,
  throttle,
  delay,
} from "redux-saga/effects";
import axios from "axios";

//thunk에서는 비동기 actionCreator을 직접 실행했지만 saga에서는 비동기 actionCreator가 이벤트 리스너 같은 역할을 한다.

function logInAPI(data) {
  // 이런식으로 로그인 요청을 보낼 수 있다.
  return axios.post("/api/login", data);
}

//generator을 사용하면 테스트하기가 쉽다.
const l = logIn({ type: "LOG_IN_REQUEST", data: { id: "zerocho@gmail.com" } });
l.next();
l.next();
// 이런식으로 next()를 이용해서 테스트를 할 수 있다.

function* logIn(action) {
  try {
    //  call을 사용하면 특이하게 인수를 펼쳐줘야한다.
    //  로그인 요청의 결과값
    // const result = yield call(logInAPI, action.data);

    //  서버가 없을때 사용하는 임시 코드, 비동기라 더미 데이터 같은 효과를 줄 수 있다.
    yield delay(1000);
    yield put({
      type: "LOG_IN_SUCCESS",
    });
  } catch (err) {
    //  put는 dispatch라고 생각하면된다.
    yield put({
      type: "LOG_IN_FAILURE",
      data: err.response.data,
    });
  }
}

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

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

function addPostAPI(data) {
  return axios.post("/api/post");
}

function* addPost(action) {
  try {
    // const result = yield call(addPostAPI, action.data);
    yield delay(1000);
    yield put({
      type: "ADD_POST_SUCCESS",
      data: result.data,
    });
  } catch (err) {
    yield put({
      type: "ADD_POST_FAILURE",
      data: err.response.data,
    });
  }
}

function* watchLogIn() {
  //  take는 LOG_IN이란 action이 실행될 때 까지 기다리겠다는 의미
  //  LOG_IN이 들어오면 logIn을 실횅한다. 이것이 이벤트 리스너 같은 역할
  //  yield는 일회용이라 while(true)을 써줬는데 이는 너무 복잡해서 takeEvery를 써준다.
  //  takeLatest는 takeEvery와 비슷하지만 사용자가 실수로 로그인 버튼을 여러번 누르면 마지막 요청만 가도록 해줌(앞에 로딩중인게 있으면 없애는 개념)
  //  만약 로그인 버튼을 2번 누르면 프론트에서 백에 요청을 2번 보내고 응답을 1번 받는 것이라 백에서도 관리가 필요하다.
  // 만약 첫번째꺼만 실행하고 싶다면 takeLeading
  yield takeLatest("LOG_IN_REQUEST", logIn);
}

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

function* watchAddPost() {
  //  throttle는 takeLatest의 단점을 보완해해 시간을 걸어두고 그 시간 안에는 요청을 1번만 보낼 수 있다.
  yield throttle("ADD_POST_REQUEST", addPost, 2000);
}

export default function* rootSaga() {
  //  all 안에는 개발자가 넣고싶은 비동기 action들을 넣어준다. 안에 있는 모든 함수를 실행시켜준다.
  //  fork는 함수를 실행하는 것, call로 대체할 수 있지만 차이점이 있다.
  //  fork는 비동기 함수 호출, call은 동기 함수 호출
  yield all([fork(watchLogIn), fork(watchLogOut), fork(watchAddPost)]);
}

위 코드 안의 주석들을 잘 읽어볼 필요가 있다. all, fork, call 같은 것들을 sagaEffect라고하고 이를 잘 활용하면 좋을 것이다.

watchLogIn 안에 써 놓은 Effect들에 대한 설명을 잘 읽어봐야 한다. 추가적으로 takeLatest, throttle을 사용하는 이유는 너무 많은 요청이 서버에 보내지면 디도스 공격을 당할 수 있기 때문이다.

그리고 만약 로그인 버튼을 클릭하면 로그인 관련 saga와 reducer가 거의 동시(둘 간의 우선순위는 있다)에 실행된다. 또 saga는 request, success, failure가 한 쌍으로 이루어진다.

 

-스로틀링과 디바운싱 차이

https://www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa 

 

https://www.zerocho.com/category/JavaScript/post/59a8e9cb15ac0000182794fa

 

www.zerocho.com

제로초님의 블로그를 읽어보면 좋다. 

요약하면 스로틀링은 스크롤링 할 때 사용하고 디바운싱은 검색어를 입력할 때 주로 사용한다.

728x90
반응형
Comments