1. redux-saga
1.2 비동기 카운터 만들기
기존의 thunk 함수로 구현했던 비동기 카운터를 이번에는 redux-saga를 사용하여 구현해 보겠습니다.
우선 아래의 redux-saga 라이브러리를 설치해 주세요.
yarn add redux-saga
그리고 counter 리덕스 모듈을 열고 기존 thunk 함수를 제거합니다.
INCREMENT_ASYNC라는 액션 타입을 선언하세요. 해당 액션에 대한 액션 생성 함수를 만들고, 제너레이터 함수를 같이 만들어 줍니다.
이 제너레이터 함수를 사가(saga)라고 부르고 있습니다.
- modules/counter.js
import { createAction, handleActions } from "redux-actions";
import { delay, put, takeEvery, takeLatest } 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 안에 들어가지 않도록 화살표 문법으로 두번쨰 파라미터를 추가
export const increaseAsync = createAction(INCREASE_ASYNC, () => undefined);
export const decreaseAsync = createAction(DECREASE_ASYNC, () => undefined);
function* increaseSaga() {
yield delay(1000); // 1초 대기
yield put(increase()); // 특정 액션을 디스패치
}
function* decreaseSaga() {
yield delay(1000); // 1초 대기
yield put(decrease()); // 특정 액션을 디스패치
}
export function* counterSaga() {
// takeEvery는 들어오는 모든 액션에 대해 특정 작업을 처리해 줌
yield takeEvery(INCREASE_ASYNC, increaseSaga);
/*
takeLastest는 기존에 진행 중이던 작업이 있다면 작업을 취소 처리하고
가장 마지막으로 실행된 작업만을 수행함
*/
yield takeLatest(DECREASE_ASYNC, decreaseSaga);
}
const initalState = 0; //객체값이 아닌 숫자도 잘 작동합니다.
const counter = handleActions(
{
[INCREASE]: (state) => state + 1,
[DECREASE]: (state) => state - 1,
},
initalState
);
export default counter;
이제는 이전 루트 리듀서를 만들었던 것처럼 루트 사가를 만들어 주어야 합니다.
추후에 다른 리듀서에서도 사가를 만들어 등록하기 떄문입니다.
- modules/index.js
import { combineReducers } from "redux";
import { all } from "redux-saga/effects";
import counter, { counterSaga } from "./counter";
import sample from "./sample";
import loading from "./loading";
// 루트 리듀서 작성
const rootReducer = combineReducers({
counter,
sample,
loading,
});
export function* roottSaga() {
yield all([counterSaga()]);
}
export default rootReducer;
이제 스토어에 redux-saga 미들웨어를 등록해 주겠습니다.
- index.js
import React from "react";
import ReactDOM from "react-dom";
import { applyMiddleware, createStore } from "redux";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import rootReducer, { roottSaga } from "./modules";
import { createLogger } from "redux-logger";
import ReduxThunk from "redux-thunk";
import createSagaMiddleware from "@redux-saga/core";
const logger = createLogger();
const sagaMiddleware = createSagaMiddleware();
// redux-thunk 적용
const store = createStore(
rootReducer,
applyMiddleware(logger, ReduxThunk, sagaMiddleware)
);
sagaMiddleware.run(roottSaga);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
스토어에 미들웨어를 적용했다면 CounterContainer 컴포넌트를 App 컴포넌트에 렌더링하여 잘 동작하는지 확인해 보겠습니다.
counter 리덕스 모듈이 변경되었지만, 컨테이너 컴포넌트는 동일하겓 동작할 것입니다.
그 이유는 기존에 사용중이던 thunk 함수와 동일한 이름으로 액션 생성함수를 만들었기 때문입니다
- App.js
import React from "react";
import CounterContainer from "./containers/CounterContainer";
const App = () => {
return (
<div>
<CounterContainer />
</div>
);
};
export default App;
여기서 리덕스 개발자 도구를 적용하여 어떤 액션이 디스패치되는지 조금 더 편하게 확인해보겠습니다.
먼저 리덕스 개발자 도구 라이브러리를 설치해주세요.
yarn add redux-devtools-extension
해당 라이브러리의 composeWithDevtools 함수를 리덕스 미들웨어와 함께 사용할 때는 applyMiddeware 소스 부분을 감싸 주면 됩니다.
- index.js
import React from "react";
import ReactDOM from "react-dom";
import { applyMiddleware, createStore } from "redux";
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import rootReducer, { roottSaga } from "./modules";
import { createLogger } from "redux-logger";
import ReduxThunk from "redux-thunk";
import createSagaMiddleware from "@redux-saga/core";
import { composeWithDevTools } from "redux-devtools-extension";
const logger = createLogger();
const sagaMiddleware = createSagaMiddleware();
// redux-thunk 적용
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(logger, ReduxThunk, sagaMiddleware))
);
sagaMiddleware.run(roottSaga);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
이제 리덕스 개발자 도구를 열고 +1버튼을 빠르게 두번 클릭해 보세요.
+1 버튼을 두번 눌러보면 INCREASE_ASYNC 액션이 두번 디스패치되고, 이에 따라 INCREASE 액션도 두번 디스패치 되는걸 확인 할 수 있습니다. takeEvery를 사용하여 increaseSaga를 등록했으므로 디스패치되는 모든 increaseSaga 액션에 대해 1초 후 INCREASE 액션을 발생 시키줍니다..
-1 버튼을 두번 눌러보면 반대로 DECREASE_ASYNC 액션이 두번 디스패치되고, DECREASE 액션이 두번 디스패치 됩니다.
takeLastest를 사용한 해당 액션은 액션이 중첩되어 디스패치되었을 경우 takeLastest 특성으로 기존 것들은 무시하고 가장 마지막 액션만 처리합니다.
따라서 -1을 여러번눌러도 반영되는 결과값은 최대 -1이 됩니다.
'프론트엔드 > React' 카테고리의 다른 글
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[10] (0) | 2022.06.03 |
---|---|
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[9] (0) | 2022.06.02 |
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[7] (0) | 2022.05.01 |
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[6] (0) | 2022.04.27 |
[프론트엔드] 리덕스 미들웨어를 통한 비동기 작업 관리[5] (0) | 2022.04.26 |