리덕스에 비동기 작업들을 처리하는 리덕스 사가를 사용해보자!
본 게시글은 next.js +redux + redux-saga기준으로 작성하였다.
설치
Store에 적용하기
Store를 만들고 미들웨어를 추가해준다.
그 후 반환 전 사가 미들웨어를 실행해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { createWrapper } from "next-redux-wrapper"; import { applyMiddleware, compose, createStore } from "redux"; import reducer from "../reducers"; import { composeWithDevTools } from "redux-devtools-extension"; import createSagaMiddleware from "redux-saga";
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;
|
제너레이터
중단점이 있는 함수이다 next()를 통해 다음 중단점 전까지 실행한다.
return된 값은 value에 저장된다.
saga에서는 이벤트 리스너로 활용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13
| const func = function* () { console.log(1); yield; console.log(2); yield; console.log(3); yield; };
func; func.next(); func.next(); func.next();
|
saga에서의 제너레이터 적용
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
| function* logIn(action) { try { const result = yield call(logInAPI, action.data); yield put({ type: "LOG_IN_SUCCESS", data: result.data, }); } catch (err) { yield put({ type: "LOG_IN_FAILURE", data: err.response.data, }); } }
function* watchLogIn() { yield take("LOG_IN_REQUEST", logIn); }
function* watchLogOut() { yield take("LOG_OUT_REQUEST"); }
export default function* rootSata() { yield all([fork(watchLogIn), fork(watchLogOut)]); }
|
saga 이펙트
sagas/index.js에 이펙트를 작성하였다.
all
1 2 3
| export default function* rootSata() { yield all([fork(watchLogIn), fork(watchLogOut)]); }
|
fork
- 매개변수로 받은 함수를 비동기 실행하는 함수이다.
1 2 3
| export default function* rootSata() { yield all([fork(watchLogIn), fork(watchLogOut)]); }
|
call
- 매개변수로 받은 함수를 동기 실행하는 함수이다.
- call(함수,매개변수1,매개변수2,…)의 형식으로 호출해주어야 한다.
- 아래 함수의 경우 call 아래줄은 500ms동안 지연이 일어난 후에 실행된다.
1 2 3 4
| function* handleInput(input) { yield call(delay, 500); }
|
take
- 해당 액션이 실행될 때 까지 기다리는 함수이다.
LOG_IN_REQUEST
액션 발생시 logIn함수를 실행한다.
1 2 3
| function* watchLogIn() { yield take("LOG_IN_REQUEST", logIn); }
|
put
- store에게 dispatch하는 이펙트이다.
- saga 미들웨어가 실행된 store에 전달한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function* logIn(action) { try { const result = yield call(logInAPI, action.data); yield put({ type: "LOG_IN_SUCCESS", data: result.data, }); } catch (err) { yield put({ type: "LOG_IN_FAILURE", data: err.response.data, }); } }
|
saga에서의 제너레이터 적용
액션별로 함수를 지정해주고 액션이 발생시 그 함수를 호출해준다.
이펙트 앞에는 yield를 붙여주어야 보장된다.
rootSaga에서 해당 이벤트가 발생시 핸들러에게 action을 같이 넘긴다.
기존 redux와 같이 action.data, action.type로 이벤트 타입과 같이 넘겨진 데이터에 접근할 수 있다.
1 2 3 4 5 6 7 8 9 10
| import { all, fork, take, call, put } from "redux-saga/effects"; import axios from "axios";
function logInAPI(data) { return axios.post("/api/login", data); }
function* watchLogOut() { yield take("LOG_OUT_REQUEST"); }
|
기타 saga 이펙트
take
- 일회용이다. 한 번 이벤트를 받으면 사라진다.
- while로 감싸 계속 사용할 수 있다.
1 2 3 4 5
| function* watchLogOut() { while (true) { yield take("LOG_OUT_REQUEST"); } }
|
takeEvery
- 계속해서 사용할 수 있다.
- while문을 대체할 수 있고, while문은 동기적으로 동작하지만 takeEvery는 비동기적으로 동작한다.
1 2 3
| function* watchLogOut() { yield takeEvery("LOG_OUT_REQUEST"); }
|
delay
1 2 3
| function* watchLogOut() { yield delay(1000); }
|
debounce
- 연이어 호출되는 함수들 중 마지막 함수만 호출되도록 한다. 호출스택의 제일 윗부분의 요청이 실행되면 값이 task에 저장되어 나머지가 전부 cancel된다.
delay 이용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function* handleInput(input) { yield call(delay, 500) ... } function* watchInput() { let task; while (true) { const { input } = yield take("INPUT_CHANGED"); if (task) { yield cancel(task); } task = yield fork(handleInput, input); } }
|
takeLatest 이용
1 2 3 4 5 6 7 8 9 10 11 12
| import { delay } from 'redux-saga'
function* handleInput({ input }) { yield call(delay, 500) ... }
function* watchInput() { yield takeLatest('INPUT_CHANGED', handleInput); }
|
throttle
- 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 한다.
1 2 3 4 5 6 7
| function* handleInput(input) { }
function* watchInput() { yield throttle(500, "INPUT_CHANGED", handleInput); }
|
ref
https://mskims.github.io/redux-saga-in-korean/recipes/