
흐름
'비동기' 액션 타입 선언
→ INCREASE_ASYNC, DECREASE_ASYNC 
액션 생성 함수 제작 (해당 액션에 대한)
increaseAsync, decreaseAsyncsaga(제너레이터 함수) 제작
코드 (
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()); // 특정 액션을 디스패치함.
}
function* decreaseSaga() {
  yield delay(1000); // 1초를 기다림.
  yield put(decrease()); // 특정 액션을 디스패치함.
}**
**export function* counterSaga() {
  // takeEvery: 들어오는 모든 액션에 대해 특정 작업 처리
  yield takeEvery(INCREASE_ASYNC, increaseSaga);
  // takeLatest: 가장 마지막 실행된 작업만 수행. (기존 진행 중이던 작업 있으면 취소 처리.)
  yield takeLatest(DECREASE_ASYNC, decreaseSaga);
}**
// 초기 값
const initialState = 0; // 상태는 꼭 객체일 필요 x. 숫자도 작동 가능
// 리듀서
const counter = handleActions(
  {
    [INCREASE]: (state) => state + 1,
    [DECREASE]: (state) => state - 1,
  },
  initialState,
);
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* rootSaga() {
  // all 함수: 여러 사가 합치는 역할
  yield all([counterSaga()]);
}**
export default rootReducer;
코드 (
index.js)
// (나머지 생략)
import rootReducer, **{ rootSaga }** from './modules';
**import createSagaMiddleware from 'redux-saga';**
**const sagaMiddleware = createSagaMiddleware();**
const store = createStore(
  rootReducer,
  applyMiddleware(logger, ReduxThunk, **sagaMiddleware**),
);
**sagaMiddleware.run(rootSaga);**
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);
코드 (
App.js)
import React from 'react';
**import CounterContainer from './containers/CounterContainer';**
const App = () => {
  return (
    <div>
      **<CounterContainer />**
    </div>
  );
};
export default App;
컨테이너 컴포넌트 내부 → 수정 없는 이유
해당 기능의 리덕스 모듈이 변경되었지만, 기존에 사용 중인 액션 생성 함수와 이름 동일함.
반복 코드 함수화 → 액션 타입, 디스패치, loading 처리, API 호출 (lib/createRequestSaga.js)
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);**
**// createRequestSaga에 분리:** 액션 타입, API 호출, loading 메소드
**const getPostSaga = createRequestSaga(GET_POST, api.getPost);
const getUsersSaga = createRequestSaga(GET_USERS, api.getUsers);**
**// Redux-saga**
**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;
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)); // 로딩 시작
    // 파라미터로 action을 받아 오면 액션의 정보 조회 가능
    try {
      // call 사용 시, Promise 반환 함수 호출하고 기다릴 수 있음.
      const reponse = yield call(request, action.payload); // 의미: request(action.payload)
      yield put({
        type: SUCCESS,
        payload: reponse.data,
      });
    } catch (e) {
      // try/catch 문 사용하여 에러 잡기 가능
      yield put({
        type: FAILURE,
        payload: e,
        error: true,
      });
    }
    yield put(finishLoading(type)); // 로딩 끝
  };
}
참고
요청에 필요한 값 전송하는 법
경우 예시
  GET_POST 액션의 경우 → api요청 시 어떤 id로 조회할지 정해 주어야 함.
액션의 payload: 요청에 필요한 값
사가(액션 처리 하기 위한) 작성 시
→ API 호출 함수의 인수: payload값
API 호출 시
call 함수 사용 (사가 내부 직접 호출 x)코드 (
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,
  sample,
  loading,
});
export function* rootSaga() {
  // all 함수: 여러 사가 합치는 역할
  yield all([counterSaga(), **sampleSaga()**]);
}
export default rootReducer;
App.js
import React from 'react';
import SampleContainer from './containers/SampleContainer';
const App = () => {
  return (
    <div>
      <SampleContainer />
    </div>
  );
};
export default App;
어떤 액션이 디스패치 되고 있는지 확인 → 편리
$ yarn add redux-devtools-extension
index.js)composeWithDevTools→ 리덕스 미들웨어와 함께 사용 시
  applyMiddleware 부분 감싸기
코드
    // (생략)
    **import { composeWithDevTools } from 'redux-devtools-extension';**
    
    const store = createStore(
      rootReducer,
      **composeWithDevTools(**applyMiddleware(logger, ReduxThunk, sagaMiddleware)**)**,
    );
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root'),
    );
참고