리덕스에서 리듀서는 순수함수여야 하는 규칙이 있습니다. 지키지 않는다고 오류가 나는 것은 아니지만, 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',
});
npm i redux-saga
npm i redux-devtools-extension
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);
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);
리덕스 사가는 자체 가지고 있는 effect들이 많고, es6+ 문법에 새로 나타난 generator function을 사용하며 이는 functional programming개념과 비슷합니다. 이런 다른 점들 때문에 saga를 사용하기 어려워합니다. saga로 사이드이펙트를 만들 때 쓰이는 effects들을 우선 정리해보겠습니다.
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),
]);
}
user action 이랑 reducer도 있으면 더 좋을거같아요~