일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 인터섹션
- React
- dfs
- 공변성
- 투포인터
- Jest
- 반공변성
- app router
- async/await
- map
- TS
- autosize
- Promise
- CORS
- 이분 검색
- SSR
- 결정 알고리즘
- tailwind
- RTK Query
- 타입 좁히기
- webpack
- useAppDispatch
- recoil
- Cypress
- ESlint
- 무한 스크롤
- 호이스팅
- 리터럴 타입
- CI/CD
- 태그된 유니온
- Today
- Total
짧은코딩
redux-saga 본문
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
제로초님의 블로그를 읽어보면 좋다.
요약하면 스로틀링은 스크롤링 할 때 사용하고 디바운싱은 검색어를 입력할 때 주로 사용한다.
'인프런, 유데미 > Redux' 카테고리의 다른 글
combineReducers의 중요성, useAppDispatch 훅스 사용시 유의점 (0) | 2022.12.07 |
---|---|
리덕스 툴킷 TS로 리액트와 연동하기(기본 폴더 구조) (1) | 2022.11.28 |
redux-toolkit (0) | 2022.11.19 |
immer(코드를 줄일 수 있음) (0) | 2022.11.18 |
리액트와 리덕스 연결하기, redux devtools (0) | 2022.11.18 |