redux-saga는 리덕스의 미들웨어
Redux-thunk와 Redux-saga가 대표적인데, Redux-thunk의 경우 saga에 대비해 쉬운 코드와 높은 생산성이 있지만, 태생적인 한계 때문에 복잡한 비동기 처리가 어렵다.
Saga의 경우 thunk에 비해서 더 복잡한 비동기 처리를 할 수 있다는 점을 들 수 있다.
redux-saga 설치하기
$ npm install redux-saga
$ yarn add 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;
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는 이벤트리스너처럼 활용이 가능하다.
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
정해놓은 시간동안 비동기적인 효과를 줌
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)]);
}