이제.. redux-saga를 맛봐야할 차례가 되엇네염.. 어제 thunk를 공부하면서 도무지 이해가 되지않아.. 그리고 왜 success니 failure니 뭐니 이딴거를 왜 설정하니.. 이해가 되지 않아.. 포기하고싶었으며.. 이해가 되지 않았으며.. 버리고싶었읍니다..
그래.. 리듀서 함수 내에서 비동기 로직 못쓰니까 thunk 쓰는구나. 그래 알겟는데 왜.. success, failure 이런 성공 실패 구분을 나누지? 싶었습니다.
하지만 비동기 로직을 짤 때 원래 try와 catch문을 쓰는 것은 일반적인 로직이고.. thunk에서 dispatch를 받아오는 이유와 (왜받아와서 왰즤?라는의문이 들었었기에..) 그리고 Promise의 구현을 thunk에서 하는 것이라고 깨우치고 난 뒤 조금 이해가 되기 시작하여 saga친구를 공부해볼까합니다.
아래 링크는 제가 thunk에 대해 고민과 조금의 이해가 되었던 부분을 정리하여 포스팅했습니다.
*[리덕스로 비동기 작업을 수행해야할 때...]
리덕스.. 부터 react-redux, RTK까지 .. 오느라 참 힘들었씁니다.. 이게 2주나 걸릴 줄이야.. 우리는 리덕스의 3가지 규칙을 배웠습니다. 상태는 read-only이다. 하나의 애플리케이션에는 스토어가 1개이
ddaeunbb.tistory.com
사가••• 정말 하기 실취만.. 너를 맞이..해볼게..
● Redux-saga
redux-thunk는 함수 형태의 액션을 디스패치해서 미들웨어에서 해당 함수에 스토어의 dispatc와 getState를 파라미터로 넣어서 사용하는 원리입니다. 따라서 thunk함수 즉, 액션 생성자 함수 내에서 dispatch도할 수 있었고, 상태 조회도 할 수 있었죵
redux-saga도 비동기 작업 관련 미들웨어 입니다. 그런데 사가같은 경우는 좀 더 까다로운 상황에서 유용합니다.
- 기존 요청을 취소 처리해야 할 때
- 특정 액션이 발생했을 때, 다른 액션을 발생시키거나 API 요청 등 리덕스와 관계없는 코드를 실행할 때
- 웹소켓을 사용할 때
- API 요청 실패 시 재요청을해야 할 때
saga에서는 ES6문법인 제너레이터 함수를 사용합니다. 제너레이터가 뭐냐구용? 나도몰?루 ㅋ
[46장 제너레이터와 async/await]
제너레이터.. 오지말라고 그렇게 협박을 했거늘.. 하지만 마주하게 되었습니다. [34장 이터러블] 시작되었읍니다.. 제너레이터로 가기 이전의 첫번째 지옥열차를 탑승하신 것을 축하합니다.. 이
ddaeunbb.tistory.com
● 제너레이터 함수 이해하기
- redux-saga에서는 ES6의 제너레이터 함수라는 문법을 사용한다.
- 제너레이터 함수는 함수에서 값을 순차적으로 반환할 수 있다.
- 제너레이터 함수를 만들 때는 function* 키워드를 사용한다.
- 제너레이터 함수에서 반환값은 return이 아닌 yield를 사용한다.
- 호출할 때는 함수를 실행시켜 변수에 할당하고 그 함수에서 next 메소드를 실행시켜 부른다.
- next 메소드에 파라미터를 넣으면 제너레이터 함수에서 yield를 사용하여 해당 값을 조회할 수 있다.
- 제너레이션 함수를 여기서는 사가(saga)라고 부른다.
● 리덕스 사가 실습하기
// index.js
import React from "react";
import ReactDOM from "react-dom";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import App from "./App";
import rootReducer, { rootSaga } from "./modules";
import { createLogger } from "redux-logger";
import ReduxThunk from "redux-thunk";
import creasteSagaMiddleware from "redux-saga";
import { composeWithDevTools } from "redux-devtools-extension";
const rootElement = document.getElementById("root");
const logger = createLogger();
const sagaMiddleware = creasteSagaMiddleware();
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(logger, ReduxThunk, sagaMiddleware))
);
sagaMiddleware.run(rootSaga);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
rootElement
);
// src/modules/index.js
import { combineReducers } from "redux";
import { all } from "redux-saga/effects";
import counter, { counterSaga } from "./counter";
import sample, { sampleSaga } from "./sample";
import loading from "./loading";
const rootReducer = combineReducers({
counter,
loading,
sample
});
export function* rootSaga() {
// all 함수는 여러 사가를 합쳐 주는 역할
yield all([counterSaga(), sampleSaga()]);
}
export default rootReducer;
// src/modules/counter.js
import { createAction, handleActions } from "redux-actions";
import {
delay,
put,
takeEvery,
takeLatest,
select,
throttle
} from "redux-saga/effects";
const INCREASE = "counter/INCREASE";
const DECREASE = "counter/DECREASE";
const INCREASE_ASYNC = "counter/INCREASE_ASYNC";
const DECREASE_ASYNC = "counter/DECREASE_ASYNC";
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
// 마우스 클릭 이벤트가 payload 안에 들어가지 않도록
// () => undefined를 두 번째 파라미터로 넣어 준다.
export const increaseAsync = createAction(INCREASE_ASYNC, () => undefined);
export const decreaseAsync = createAction(DECREASE_ASYNC, () => undefined);
function* increaseSaga() {
yield delay(1000); // 1초 기다리기
yield put(increase()); // 액션 디스패치하기
const number = yield select((state) => state.counter);
console.log(`현재 값은 ${number}입니다`);
}
function* decreaseSaga() {
yield delay(1000);
yield put(decrease());
}
export function* counterSaga() {
// takeEvery는 들어오는 모든 액션에 대해 특정 작업을 처리해 준다.
yield throttle(3000, INCREASE_ASYNC, increaseSaga);
// takeLatest는 기존에 진행 중이던 작업이 있다면 취소 처리하고
// 가장 마지막으로 실행된 작업만 수행합니다.
yield takeLatest(DECREASE_ASYNC, decreaseSaga);
}
const initialState = 0;
const counter = handleActions(
{
[INCREASE]: (state) => state + 1,
[DECREASE]: (state) => state - 1
},
initialState
);
export default counter;
// src/modules/sample.js
import { createAction, handleActions } from "redux-actions";
import * as api from "../lib/api";
import { call, put, takeLatest } from "redux-saga/effects";
import { startLoading, finishLoading } from "./loading";
import createRequestSaga from "../lib/createRequestSaga";
const GET_POST = "sample/GET_POST";
const GET_POST_SUCCESS = "sample/GET_POST_SUCCESS";
const GET_USERS = "sample/GET_USER";
const GET_USERS_SUCCESS = "sample/GET_USER_SUCCESS";
export const getPost = createAction(GET_POST, (id) => id);
export const getUsers = createAction(GET_USERS);
const getPostSaga = createRequestSaga(GET_POST, api.getPost);
const getUsersSaga = createRequestSaga(GET_USERS, api.getUsers);
export function* sampleSaga() {
yield takeLatest(GET_POST, getPostSaga);
yield takeLatest(GET_USERS, getUsersSaga);
}
const initialState = {
post: null,
users: null
};
const sample = handleActions(
{
[GET_POST_SUCCESS]: (state, action) => ({
...state,
post: action.payload
}),
[GET_USERS_SUCCESS]: (state, action) => ({
...state,
users: action.payload
})
},
initialState
);
export default sample;
// src/modules/loading.js
import { createAction, handleActions } from "redux-actions";
const START_LOADING = "loading/START_LOADING";
const FINISH_LOADING = "loading/FINISH_LOADING";
// 요청을 위한 액션 타입을 payload로 설정한다 (예: "sample/GET_POST")
export const startLoading = createAction(
START_LOADING,
(requestType) => requestType
);
export const finishLoading = createAction(
FINISH_LOADING,
(requestType) => requestType
);
const initialState = {};
const loading = handleActions(
{
[START_LOADING]: (state, action) => ({
...state,
[action.payload]: true
}),
[FINISH_LOADING]: (state, action) => ({
...state,
[action.payload]: false
})
},
initialState
);
export default loading;
// src/lib/createRequestSaga.js
import { call, put } from "redux-saga/effects";
import { startLoading, finishLoading } from "../modules/loading";
export default function createRequestSaga(type, request) {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return function* (action) {
yield put(startLoading(type));
try {
const response = yield call(request, action.payload);
yield put({
type: SUCCESS,
payload: response.data
});
} catch (e) {
yield put({
type: FAILURE,
payload: e,
error: true
});
}
yield put(finishLoading(type));
};
}
● Redux-saga 총정리
리덕스 사가는 드리븐, 스로틀 구현이 다 되어있어서 편할 것 같다는 생각이 드네요.. 내가 써먹을 수 있을지는 몰?루 ㅎ
그리고 만약에 api요청을 갑자기 개많이 했다면 takeLatest를 통해서도 딱한번만 하게 할 수도 있을것같고..
조금 더 섬세한 비동기 처리를 원한다면 saga를 쓰는게 맏겠네요. 하지만 갑자기 thunk가 쉬워지는 것 같은 매직 ㅎ