๋ฏธ๋ค์จ์ด๋ ๋ฆฌ๋์ค์์ ๋ ๋ง์ ๊ธฐ๋ฅ์ ํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฒ์ด๋ค.
๊ทธ์ค Redux-thunk , Redux-saga๊ฐ ๋ง์ด ์ฐ์ด๊ฒ ๋๋๋ฐ,
๋น๋๊ธฐ ์์
์ ์์ด์ ํจ์จ์ ์ด๋ค..
Redux-thunk๋ ๋น๋๊ธฐ ์ก์
ํจ์ ํ๋์์ dispatch๋ฅผ ์ฌ๋ฌ๋ฒ ์ฆ ๋น๋๊ธฐ ์ก์
์ ์ค ์ ์๋ค.
์ฝ๊ฒ ๋งํด dispatch์ฌ๋ฌ๊ฐ๋ฅผ ํ๋ฒ์ ๋ฌถ์ด์ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค๋ค.
๋จผ์ 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;
import React from "react";
import Head from "next/head";
import "antd/dist/antd.css";
import wrapper from "../store/configureStore";
import withReduxSaga from "next-redux-saga"; //next์ redux-saga๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋ library
function _app({ Component }) {
return (
<>
<Head>
<meta charSet="utf-8"></meta>
<title>Wongeun</title>
</Head>
<Component />
</>
);
}
//๊ฐ์ธ์ค๋ค.
export default wrapper.withRedux(withReduxSaga(_app)); //saga๋ก ํ๋ฒ๋ ๋ฎ์ด์ค๋ค.
๋ค์์ 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์ ๊ทธ๊ฒ๋ค์ ๋์์ ์คํํ ์ ์๊ฒ ๋ง๋ฌ.
]);
}