Redux middleware

๋ฏธ๋“ค์›จ์–ด๋Š” ๋ฆฌ๋•์Šค์—์„œ ๋” ๋งŽ์€ ๊ธฐ๋Šฅ์„ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

๊ทธ์ค‘ Redux-thunk , Redux-saga๊ฐ€ ๋งŽ์ด ์“ฐ์ด๊ฒŒ ๋˜๋Š”๋ฐ,

๋น„๋™๊ธฐ ์ž‘์—…์— ์žˆ์–ด์„œ ํšจ์œจ์ ์ด๋‹ค..

Redux-thunk๋Š” ๋น„๋™๊ธฐ ์•ก์…˜ํ•จ์ˆ˜ ํ•˜๋‚˜์—์„œ dispatch๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ์ฆ‰ ๋น„๋™๊ธฐ ์•ก์…˜์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•ด dispatch์—ฌ๋Ÿฌ๊ฐœ๋ฅผ ํ•œ๋ฒˆ์— ๋ฌถ์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.

yarn add redux-thunk

๋จผ์ € redux-thunk๋ฅผ ์„ค์น˜ํ•ด์ค€๋‹ค.

๊ทธ ๋’ค ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“œ๋Š” ๊ณณ์—์„œ middleware๋ฅผ ์ ์šฉํ•ด์ค€๋‹ค.

import { createWrapper } from "next-redux-wrapper";
import { createStore, applyMiddleware, compose } from "redux";
import reducer from "../reducers";
import { composeWithDevTools } from "redux-devtools-extension";
import ReduxThunk from "redux-thunk"; //import

const configureStore = () => {
  const middlewares = [ReduxThunk]; //์ ์šฉ
  const enhancer =
    process.env.NODE_ENV === "production"
      ? compose(applyMiddleware(...middlewares)) //๋ฐฐํฌ์šฉ
      : composeWithDevTools(applyMiddleware(...middlewares)); //๊ฐœ๋ฐœ์šฉ
  const store = createStore(reducer, enhancer);
  return store;
};
const wrapper = createWrapper(configureStore, {
  debug: process.env.NODE_ENV === "development",
});
export default wrapper;

๋ฏธ๋“ค์›จ์–ด๋Š” 3๋‹จ ํ•จ์ˆ˜์ธ๋ฐ, ์‚ฌ์‹ค ์•„๋ž˜์˜ ์ฝ”๋“œ๊ฐ€ ์ „๋ถ€์ด๋‹ค.

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {  //์•ก์…˜์ด ํ•จ์ˆ˜์ธ๊ฒฝ์šฐ 
      return action(dispatch, getState, extraArgument);  //๋น„๋™๊ธฐ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค.
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
ยฉ 2020 GitHub, Inc.

thunk๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š”

๋กœ๊ทธ์ธ์„ ์›ํ•˜๊ณ  ๋กœ๊ทธ์•„์›ƒ์„ ํ•œ๋‹ค๊ณ  ํ•ด์„œ ๋ฐ”๋กœ ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ผ,

๋ฐฑ์•ค๋“œ์„œ๋ฒ„๋ฅผ ๊ฑฐ์น˜๊ธฐ ๋•Œ๋ฌธ์— Request Action์ด ๋˜๊ฒŒ ๋˜๋Š”๋ฐ,

๋น„๋™๊ธฐ ์š”์ฒญ์€ LOADING, SUCCESS, ERROR 3๋‹จ๊ณ„๋กœ ๋ณดํ†ต ๋Œ€๋‹ค์ˆ˜๊ฐ€ ์ด๋ฃจ์–ด์ง„๋‹ค.

์˜ˆ๋ฅผ๋“ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

export const getPosts = () => async dispatch => {
  dispatch({ type: GET_POSTS }); // ์š”์ฒญ์ด ์‹œ์ž‘๋จ
  try {
    const posts = await postsAPI.getPosts(); // API ํ˜ธ์ถœ
    dispatch({ type: GET_POSTS_SUCCESS, posts }); // ์„ฑ๊ณต
  } catch (e) {
    dispatch({ type: GET_POSTS_ERROR, error: e }); // ์‹คํŒจ
  }
};

๋ฌผ๋ก  dispatch ๋ง๊ณ ๋„ getState๋ฅผ ๋ฐ›์•„์™€์„œ ํ•จ์ˆ˜์•ˆ์—์„œ ์ƒํƒœ๋ฅผ ์กฐํšŒํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด , thunk์˜ ๊ธฐ๋Šฅ์€ ์—ฌ๊ธฐ์„œ ๋๋‚œ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด Saga๋ฅผ ๋” ๋งŽ์ด ์“ฐ์ด๋Š” ์ด์œ ๋Š” ๋ญ˜๊นŒ?

Saga์—์„œ๋Š” delay๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์œผ๋ฉฐ,

์‹ค์ˆ˜๋กœ ํด๋ฆญ์„ ๋‘๋ฒˆํ–ˆ์„๊ฒฝ์šฐ ์ฒซ๋ฒˆ์งธ ํด๋ฆญ์€ ๋ฌด์‹œํ•˜๊ณ , ๋งˆ์ง€๋ง‰ ํด๋ฆญ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

์ด๋Ÿฌํ•œ๊ฒฝ์šฐ Front server์—์„œ DDOS๊ณต๊ฒฉ์„ ํ• ์ˆ˜๋„ ์žˆ๊ณ ,

์ตœ์•…์˜๊ฒฝ์šฐ Self -ddos๊ณต๊ฒฉ์„ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

saga์˜ throttle์„ ์ด์šฉํ•ด ๊ทธ๊ฑธ ๋ง‰์„์ˆ˜ ์žˆ์œผ๋‹ˆ ํ•œ๋ฒˆ ๋ฐฐ์›Œ๋ด์•ผ ๊ฒ ๋‹ค.

next์—์„œ๋Š” saga์˜ ์ ์šฉ๋ฒ•์ด ์กฐ๊ธˆ ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜๋ฅผ ์„ค์น˜ํ•ด์ฃผ์ž

yarn add next-redux-saga
import { createWrapper } from "next-redux-wrapper";
import { createStore, applyMiddleware, compose } from "redux";
import reducer from "../reducers";
import { composeWithDevTools } from "redux-devtools-extension";
import createSagaMiddleware from "redux-saga";
import rootSaga from "../sagas"; //๋ฏธ๋ฆฌ import

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === "function") {
      return action(dispatch, getState, extraArgument);
    }

    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); //์ถ”๊ฐ€(rootSaga๋Š” ์ง์ ‘ ์ž‘์„ฑ)
  return store;
};
const wrapper = createWrapper(configureStore, {
  debug: process.env.NODE_ENV === "development",
});
export default wrapper;

๋‹ค์Œ์€ sagasํด๋” ์ƒ์„ฑ

function* = generatorํ•จ์ˆ˜

generatorํ•จ์ˆ˜๋Š” .next()๋ฅผ ๋ถ™์—ฌ์ค˜์•ผ ์‹คํ–‰๋œ๋‹ค.

yeild๊ฐ€ ์žˆ๋Š”๊ณณ์—์„œ ๋ฉˆ์ถ”๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๋‹จ์ ์ด ์žˆ๋Š” ํ•จ์ˆ˜์ด๋‹ค.

๊ทธ๋ž˜์„œ saga์—์„œ๋Š” while(true) ๋ฐ˜๋ณต๋ฌธ๋„ ๋ฌดํ•œ๋ฐ˜๋ณต์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.

์‹ค์ œ๋กœ JS์—์„œ ๋ฌดํ•œ์˜ ๊ฐœ๋…์„ ํ‘œํ˜„ํ•˜๊ณ  ์‹ถ์„ ๋•Œ generator๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๋˜ํ•œ ํŠน์ • ์ด๋ฒคํŠธ(.next())๊ฐ€ ์‹คํ–‰๋˜์—ˆ์„๋•Œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— EventListner๋กœ ๋ณผ์ˆ˜์žˆ๋‹ค.

saga์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋‚ด gitbook์— ์žˆ๋Š” ๋‚ด์šฉ๊ณผ ๋™์ผ

import { all, fork, take, call, put } from "redux-saga/effects";
import axios from "axios";
function logInAPI(data) {
  //์ œ๋„ค๋ ˆ์ดํ„ฐ ์•„๋‹˜!!
  const response = axios.post("/api/login");
  return response.data;
}
function addPostAPI(data) {
  const response = axios.post("/api/post");
  return response.data;
}
function* addPost(action) {
  yield put({
    type: "ADD_POST_REQUEST",
  });
  try {
    const data = yield call(addPostAPI, action.data);
    yield put({
      type: "ADD_POST_SUCCESS",
      data,
    });
  } catch (e) {
    yield put({
      type: "ADD_POST_ERROR",
      data: e.response.data,
    });
  }
}
function* logIn(action) {
  yield put({
    type: "LOG_IN_REQUEST",
  });
  try {
    const data = yield call(logInAPI, action.data);
    yield put({
      type: "LOG_IN_SUCCESS",
      data,
    });
  } catch (e) {
    yield put({
      type: "LOG_IN_ERROR",
      data: e.response.data,
    });
  }
}

function* logOut() {
  yield put({
    type: "LOG_OUT_REQUEST",
  });
  try {
    const data = yield call(logInAPI);
    yield put({
      type: "LOG_OUT_SUCCESS",
      data,
    });
  } catch (e) {
    yield put({
      type: "LOG_OUT_ERROR",
      data: e.response.data,
    });
  }
}
function* watchLogin() {
  yield takeEvery("LOG_IN_REQUEST", logIn); //LOG_IN์ด๋ผ๋Š” ์•ก์…˜์ด ์‹คํ–‰๋ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค. ์‹คํ–‰๋˜๋ฉด logIn์‹คํ–‰
}
function* watchLogOut() {
  yield takeEvery("LOG_OUT_REQUEST", logOut);
}
function* watchAddPost() {
  yield takeEvery("ADD_POST_REQUEST", addPost);
}

export default function* rootSaga() {
  // all์€ ๋ฐฐ์—ด์„ ๋ฐ›์•„ ํ•œ๋ฐฉ์— ์‹คํ–‰ํ•ด์ค€๋‹ค
  yield all([
    //fork๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
    fork(watchLogin), //call์ด๋ž‘ ๊ตฌ๋ถ„๋˜์ง€๋งŒ ์ง€๊ธˆ์€ ์ƒ๊ด€์—†๋‹ค..
    fork(watchLogin), //์ฐจ์ด์ ์€ call์€ ๋น„๋™๊ธฐ ์‹คํ–‰์ด์ง€๋งŒ, fork๋Š” ๋™๊ธฐ์ด๋‹ค.
    fork(watchLogin),
    //ํ•œ๋งˆ๋””๋กœ fork, call์€ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š”๋ฐ all์€ ๊ทธ๊ฒƒ๋“ค์„ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ฌ.
  ]);
}

Last updated