미들웨어 연결하기 redux-saga

김수정·2020년 1월 8일
0

리덕스 이해하기

목록 보기
5/6

미들웨어를 사용하는 이유

리덕스에서 리듀서는 순수함수여야 하는 규칙이 있습니다. 지키지 않는다고 오류가 나는 것은 아니지만, state가 동일한지 여부를 따져서 리덕스가 동작하기 때문에 state를 만드는 리듀서 자체에 같은 인풋에 따른 같은 아웃풋 값을 보장할 수 없다면 불필요한 연산이 실행될 수 있습니다. 따라서 리듀서를 순수함수로 남겨두기 위해 redux-saga나 redux-thunk같은 미들웨어를 사용합니다.

보통 미들웨어를 사용하는 사이드 이펙트는 서버에서 보내주는 API응답 값입니다. 그러므로 미들웨어를 설치하기 전 http통신을 도와주는 라이브러리인 axios를 설치하겠습니다.

npm i axios

저는 axios를 한 번 더 감싸서 export하는데요. 이렇게 하면 공통 작업들을 한 곳에서 할 수 있기 때문입니다.

import axios from 'axios';

export const httpClient = axios.create({
  baseURL: 'http://localhost:3065/api',
});

redux-saga, 스토어에 연결하기

설치

npm i redux-saga

연결

npm i redux-devtools-extension

1) react.js버전

import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from './reducers';
import rootSaga from '../sagas';
...
const sagaMiddleware = createSagaMiddleware();
const middleware = [sagaMiddleware];
const enhancer = process.env.NODE_ENV === 'production'
	? compose(applyMiddleware(...middleware)
    : composeWithDevTools(applyMiddleware(...middleware);
const store = createStore(rootReducer, );
sagaMiddleware.run(rootSaga);

2) next.js버전

import withRedux from 'next-redux-wrapper';
import { applyMiddleware, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducers';
import rootSaga from '../sagas';

const ReactBird = () => {
  return (<div></div>);
}

export default withRedux((initialState) => {
  const sagaMiddleware = createSagaMiddleware();
  const middlewares = [sagaMiddleware];
  const enhancer = process.env.NODE_ENV === 'production'
    ? compose(applyMiddleware(...middlewares))
    : composeWithDevTools(
      applyMiddleware(...middlewares),
    );
  const store = createStore(rootReducer, initialState, enhancer);
  sagaMiddleware.run(rootSaga);
  return store;
})(ReactBird);

saga만들기

saga effects

리덕스 사가는 자체 가지고 있는 effect들이 많고, es6+ 문법에 새로 나타난 generator function을 사용하며 이는 functional programming개념과 비슷합니다. 이런 다른 점들 때문에 saga를 사용하기 어려워합니다. saga로 사이드이펙트를 만들 때 쓰이는 effects들을 우선 정리해보겠습니다.

  • all: 여러 개의 사가를 묶을 때 사용
  • fork: 비동기로 함수 호출
  • call: 동기로 함수 호출
  • put: dispatch
  • take: 액션 모니터링 이벤트. 해당 액션이 dispatch되면 제너레이터를 next해주는 이펙트
    - takeEvery: while(true) { yield take ...}의 압축본. 매 take마다 실행을 반복한다는 의미.동시에 같은 take가 많이 들어와도 같은 수의 실행문을 실행함.
    • takeLatest: takeEvery와 비슷한데, 동시에 같은 take가 많이 들어오면 뒤의 실행은 최신 것 하나만 실행한다는 의미. 이전 요청이 끝나지 않은 게 있다면 이전 요청을 취소.

saga directory

saga를 연결했다면 이제 saga만 만들면 끝입니다! saga자체가 view를 건들지는 않기 때문이죠.

saga/index.js
rootSaga를 만들어서 하나의 사가로 합칩니다. 이는 위에 있는 app.js에 넣어주겠지요.

import { all, fork } from 'redux-saga/effects';
import userSaga from './user';

export default function* rootSaga() {
  yield all([
    fork(userSaga),
  ]);
}

saga/user.js
모든 api들이 saga에서 이루어지므로 saga폴더에 api도 함께 넣었습니다.
실제 saga를 하게되는 loginSaga.
watchLogin 제너레이터함수는 이벤트처럼 기다리고 있다가 해당 액션이 실행되면 두번째 인자에 있는 saga를 실행시킵니다.

이 3개 함수를 세트로 하여 saga들을 만들어냅니다.

// base
import {
  all, fork, call, put, takeEvery,
} from 'redux-saga/effects';
import { httpClient } from '../utils';
import {
  LOG_IN_REQUEST,
  loginSuccessAction,
  loginFailureAction,
} from '../reducers/user';

function loginAPI(loginData) {
  return httpClient.post('/user/login', loginData);
}

function* loginSaga(action) {
  try {
    const res = yield call(loginAPI, action.data);
    yield put(loginSuccessAction(res));
  } catch (e) {
    console.error(e);
    yield put(loginFailureAction(e.message));
  }
}

function* watchLogin() {
  yield takeEvery(LOG_IN_REQUEST, loginSaga);
}

export default function* userSaga() {
  yield all([
    fork(watchLogin),
  ]);
}
profile
정리하는 개발자

1개의 댓글

comment-user-thumbnail
2020년 10월 14일

user action 이랑 reducer도 있으면 더 좋을거같아요~

답글 달기