React로 NodeBird SNS 만들기 2 redux-saga

리덕스에 비동기 작업들을 처리하는 리덕스 사가를 사용해보자!
본 게시글은 next.js +redux + redux-saga기준으로 작성하였다.

설치

1
npm i 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{<suspended>}
func.next(); //1 {value:undefined,done:false}
func.next(); //2 {value:undefined,done:false}
func.next(); //3 {value:undefined,done:true}

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) {
// 500ms마다 지연
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); //1초 대기
}

debounce

  • 연이어 호출되는 함수들 중 마지막 함수만 호출되도록 한다. 호출스택의 제일 윗부분의 요청이 실행되면 값이 task에 저장되어 나머지가 전부 cancel된다.

delay 이용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* handleInput(input) {
// 500ms마다 지연
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 }) {
// 500ms마다
yield call(delay, 500)
...
}

function* watchInput() {
// 현재 실행중인 handleInput 작업을 취소합니다.
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/

댓글