18.3.2 redux-saga
- 기존 요청을 취소 처리해야 할 때 (불필요한 중복 요청 방지)
- 특정 액션이 발생했을 때 다른 액션을 발생시키거나, API 요청 등 리덕스와 관계없는 코드를 실행할 때
- 웹소켓을 사용할 떄
- API 요청 실패 시 재요청해야 할 때
18.3.2.1 제너레이터 함수 이해하기
제너레이터(generator) 함수
- 함수에서 값을 순차적으로 반환가능
- function* 키워드 사용
- 제너리에터 함수를 호출했을 때 반환되는 객체 = 제너레이터
- 제너레이터가 처음 만들어지면 함수의 흐름은 멈춰 있는 상태
- next()가 호출되면 다음 yield가 있는 곳까지 호출하고 다시 함수가 멈춤
function* generatorFunction() {
console.log('안녕하세요');
yield 1;
console.log('제너레이터 함수');
yield 2;
console.log('function*');
yield 3;
return 4;
}
const generator = generatorFunction();
generator.next();
generator.next();
generator.next();
generator.next();
- redux-saga는 제너레이터 함수 문법을 기반으로 비동기 작업을 관리
- redux-saga는 우리가 디스패치하는 액션을 모니터링해서 그에 따라 필요한 작업을 따로 수행할 수 있는 미들웨어
function* watchGenerator() {
console.log('모니터링중...');
let prevAction = null;
while(true) {
const action = yield;
console.log('이전 액션:', prevAction);
prevAction = action;
if(action.type === 'HELLO') {
console.log('안녕하세요!');
}
}
}
const watch = watchGenerator();
watch.next();
watch.next();
watch.next();
18.3.2.2 비동기 카운터 만들기
- 라이브러리 설치
npm install redux-saga
module/counter
import {createAction, handleActions} from 'redux-actions';
import {delay, put, takeEvery, takeLatest} from 'redux-saga/effiects';
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);
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 initialState = 0;
//리듀서 함수
const counter = handleActions(
{
[INCREASE]: state => state +1,
[DECREASE]: state => state -1
},
initialState
);
export default counter;
moduels/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* rootSaga() {
//all 함수는 여러 사가를 합쳐 주는 역할
yield all([counterSaga()]);
}
export default rootReducer;
index.js
- 스토어에 redux-saga 미들웨어 적용
import ReactDOM from 'react-dom/client';
import App from './App';
import { applyMiddleware, createStore } from 'redux';
import rootReducer, { rootSaga } from './modules';
import { Provider } from 'react-redux';
import {createLogger} from 'redux-logger';
import {thunk} from 'redux-thunk';
import createSagaMiddleware from 'redux-saga';
const logger = createLogger();
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(logger, thunk, sagaMiddleware)); //스토어 생성
sagaMiddleware.run(rootSaga);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App/>
</Provider>
);
- 리덕스 개발자 도구 라이브러리 설치
npm install redux-devtools-extension
18.3.2.3 API 요청 상태 관리하기
lib/createRequestSaga.js
- API를 호출해야 하는 상황에는 사가 내부에서 직접 호출하지 않고 call 함수를 사용
- call 함수의 경우, 첫 번째 인수는 호출하고 싶은 함수이고, 그 뒤에 오는 인수들은 해당 함수에 넣어 주고 싶은 함수
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)); //로딩 끝
}
}
modules/index.js
- sampleSaga를 루트 사가에 등록
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,
sample,
loading
});
//루트 사가
export function* rootSaga() {
//all 함수는 여러 사가를 합쳐 주는 역할
yield all([counterSaga(), sampleSaga()]);
}
export default rootReducer;
modules/sample.js
import { createAction, handleActions } from "redux-actions";
import { takeLatest } from "redux-saga/effects";
import * as api from '../lib/api';
import createRequestSaga from "../lib/createRequestSaga";
//액션 타입을 선언
const GET_POST = 'sample/GET_POST'; //시작
const GET_POST_SUCCESS = 'sample/GET_POST_SUCCESS'; //성공
const GET_USERS = 'sample/GET_USERS'; //시작
const GET_USERS_SUCCESS = 'sample/GET_USERS_SUCCESS'; //성공
export const getPost = createAction(GET_POST, id=>id);
export const getUsers = createAction(GET_USERS);
export const getPostSaga = createRequestSaga(GET_POST, api.getPost);
export const getUsersSaga = createRequestSaga(GET_USERS, api.getUsers);
export function* sampleSaga() {
yield takeLatest(GET_POST, getPostSaga);
yield takeLatest(GET_USERS, getUsersSaga);
}
//초기 상태 선언
//요청이 로딩 중 상태는 loading이라는 객체에서 관리
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;
'react' 카테고리의 다른 글
[리액트를 다루는 기술] 21장 백엔드 프로그래밍 : Node.js의 Koa 프레임워크 (0) | 2023.12.12 |
---|---|
[리액트를 다루는 기술] 19장 코드 스플리팅 (0) | 2023.12.11 |
[리액트를 다루는 기술] 18장 리덕스 미들웨어를 통한 비동기 작업 관리(1) (1) | 2023.12.09 |
[리액트를 다루는 기술] 17장 리덕스를 사용하여 리액트 애플리케이션 상태 관리하기 (0) | 2023.11.24 |
[리액트를 다루는 기술] 16장 리덕스 라이브러리 이해하기 (0) | 2023.11.22 |