Redux는 동기적으로 실행(바로 실행)되기 때문에 비동기적 명령을 내릴 수 없다. 이는 미들웨어인 Redux-saga를 쓰는 이유가 된다(비동기 동작 실행).
주로 서버에 비동기 요청을 할 때 컴포넌트에서 요청하지 않고 Saga에서 모아서 관리해주기 위하여 사용한다.
Saga는 이터레이터(iterator)의 next 메서드를 이펙트에 따라 알아서 해주는 제너레이터(generator)이다.
currying
기법을 사용한다.
const middleware = (store) => (next) => (action) => {
console.log(action) // 다른 작업들을 여기에서 실행할 수 있도록
next(action);
};
sagas/user.js
import { all, fork, takeLatest, call, put } from 'redux-saga/effects';
import { LOG_IN, LOG_IN_SUCCESS, LOG_IN_FAILURE } from '../reducers/user';
function loginAPI() {
// 서버에 요청을 보내는 부분
}
function* login() {
try {
yield call(loginAPI);
yield put({ //put은 dispatch와 동일
type: LOG_IN_SUCCESS,
});
} catch (e) {
console.error(e); // loginAPI 실패
yield put({
type: LOG_IN_FAILURE,
});
}
}
function* watchLogin() {
yield takeLatest(LOG_IN, login);
}
export default function* userSaga() {
yield all([fork(watchLogin)]);
}
call
은 함수 동기적 호출
fork
는 함수 비동기적 호출
put
은 액션 dispatch
Next.js를 사용하고 있다면 Redux를 사용하기 위해 next-redux-wrapper 라이브러리를 사용해야한다.
yarn add 'next-redux-wrapper'
또는
npm -i 'next-redux-wrapper'
예시
import React from 'react';
import Head from 'next/head';
import PropTypes from 'prop-types';
import withRedux from 'next-redux-wrapper'; // next-redux-wrapper 라이브러리 import
import AppLayout from '../components/AppLayout';
import { Provider } from 'react-redux';
import reducer from '../reducers';
import { createStore, compose, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../sagas';
const sagaMiddleware = createSagaMiddleware();
const makeStore = (initialState, options) => {
// 여기에 store 커스터마이징
const middlewares = [sagaMiddleware]; // 미들웨어는 action과 store 사이에서 동작
// const enhancer = compose(applyMiddleware([...middlewares]));
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ &&
process.env.NODE_ENV !== 'production'
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
})
: compose;
const enhancer = composeEnhancers(
applyMiddleware(...middlewares),
// other store enhancers if any
);
const store = createStore(reducer, initialState, enhancer);
sagaMiddleware.run(rootSaga);
return store;
};
function NodeBird({ Component, pageProps, store }) {
return (
<Provider store={store}>
<Head>
<title>Node Bird</title>
<link
rel='stylesheet'
href='https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.1/antd.css'
/>
</Head>
<AppLayout>
<Component {...pageProps} /> {/* Component JSX 형식으로 작성할 것 */}
{/* Next에서 pages 폴더 내의 Components를 자동적으로 props로 넣어줌 */}
</AppLayout>
</Provider>
);
}
NodeBird.propTypes = {
Component: PropTypes.elementType,
props: PropTypes.object,
};
export default withRedux(makeStore)(NodeBird); // HoC로 redux-next-wrapper, Redux, Component를 연결
// Before this, import what you need and create a root saga as usual
const makeStore = (initialState, options) => {
// 1: Create the middleware
const sagaMiddleware = createSagaMiddleware();
// Before we returned the created store without assigning it to a variable:
// return createStore(reducer, initialState);
// 2: Add an extra parameter for applying middleware:
const store = createStore(reducer, initialState, applyMiddleware(sagaMiddleware));
// 3: Run your sagas:
sagaMiddleware.run(rootSaga);
// 4: now return the store:
return store
};