import { createWrapper } from "next-redux-wrapper";
import { applyMiddleware, compose, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension"; //chrome devtools
import rootReducer from "../reducers/index";
const configureStore = () => {
const middlewares = [];
const enhancer =
process.env.NODE_ENV === "production"
? compose(applyMiddleware(...middlewares))
: composeWithDevTools(applyMiddleware(...middlewares));
const store = createStore(rootReducer, enhancer);
return store;
};
const wrapper = createWrapper(configureStore, {
debug: process.env.Node_ENV === "developoment",
});
export default wrapper;
스토어에 미들웨어를 적용하는 부분을 가져왔다.
미들웨어에 대해서 좀 더 찾아보는 것은 처음이라 기록해본다.
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers/index'
const store = createStore(
reducer,
compose(applyMiddleware(thunk), DevTools.instrument())
)
compose는 우에서 좌를 향하여 함수를 조합해 나간다.
dispatch 함수를 감싸는 고차함수
import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
function logger({ getState }) {
return next => action => {
console.log('will dispatch', action)
// Call the next dispatch method in the middleware chain.
const returnValue = next(action)
console.log('state after dispatch', getState())
// This will likely be the action itself, unless
// a middleware further in chain changed it.
return returnValue
}
}
const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
// (These lines will be logged by the middleware:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' `
단순한 미들웨어의 내부는 이런 식으로 짜여있다고 한다.
위 함수를 더욱 단순화 시키면 아래와 같다.
function middleware({getState, dispatch}}) {
return function wrapDispatch(next) {
return function dispatchToSomething(action) {
// do something...
return next(action);
}
}
}
// arrow syntax
const middleware = ({getState, dispatch}) => next => action => {
// do something...
return next(action);
}
즉 미들웨어는 getstate,dispatch
두 인자를 받아 다음 미들웨어나 (다음 미들웨어가 없다면) 리듀서로 전달하는 함수를 return 하는 것이다.
next는 다음 middleware의 dispatch method라고 한다.
createStore를 감싸는 고차함수
곱씹어보기.
function applyMiddleware(...middlewares) { // applyMiddleware는 기존 createStore의 고차 함수를 반환한다. return (createStore) => (reducer, preloadedState, enhancer) => { const store = createStore(reducer, preloadedState, enhancer) let dispatch = store.dispatch let chain = [] // 미들웨어들에게 인자로 전달되는 객체이다. // dispatch가 단순히 store.dispatch의 참조를 전단하는 것이 아니라, // 함수를 한번 더 감싸 사용하는 점을 기억하자. const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // 미들웨어들이 반환하는 체인 함수들(= wrapDispatch)을 가져온다. chain = middlewares.map(middleware => middleware(middlewareAPI)) // 미들웨어가 반환하는 체인 함수들을 중첩시킨 후 새로운 dispatch 함수를 만든다. dispatch = compose(...chain)(store.dispatch) // 즉 위에서 만들어진 각 wrapDispatch 함수들을 compose로 중첩 시켜 고차함수화 하는 부분이다. // applyMiddleware를 통해 반환된 createStore 고차 함수는 // 기존 스토어와 동일한 API, 그리고 새로 만들어진 dispatch 함수를 반환한다. return { ...store, dispatch } } }
참고 :
참고
- https://redux.js.org/understanding/history-and-design/middleware
처음부터 이 페이지를 읽었으면 좀 더 수월하지 않았을까 싶다.
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}
}
applyMiddleware
실제 코드와는 조금 다르게 이해를 위한 코드다.(redux 홈페이지에서 가져왔다.)// Warning: Naïve implementation!
// That's *not* Redux API.
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let dispatch = store.dispatch
middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)))
return Object.assign({}, store, { dispatch })
}
보면 최초의 dispatch는 store의 method지만, 그 이후는
- 이전 middleware에 store을 넣고
- 거기서 나온 함수에 dispatch를 인자로 넣은 결과인 함수를
- dispatch에 재할당한다.
따라서, 아래와 같이 정리할 수 있겠다.
applyMiddleware(logger, crashReporter)
처럼 두 가지 이상의 middleware가 적용되어 있다면const crashReporter = store => next => action => { try { return next(action) } catch (err) { console.error('Caught an exception!', err) Raven.captureException(err, { extra: { action, state: store.getState() } }) throw err } }
- crashReporter에 대해
carshReporter(stroe)(store.disaptch)
가 실행됨.
- 그 결과를 dispatch에 할당 (
dispatch = carshReporter(stroe)(store.disaptch)
)할당되는 함수는 정확하게는 아래의 부분 (action 객체가 들어오기전)
action => { try { return next(action) } catch (err) { console.error('Caught an exception!', err) Raven.captureException(err, { extra: { action, state: store.getState() } }) throw err } }
logger
미들웨어는 위의 함수를next
위치에 인자로 받아서 그것을 자신의 함수로 덮어씌운다.
(합수를 합성하는 것을 생각하면 되겠다.)
- 따라서 최종적으로 store의 dispatch는 모양이 최초와 달라지게 된다.
형태는 가장 오른쪽 미들웨어가 action 객체를 인자로 하는 store.dispatch를 감싼 함수를 -> 그 다음 왼쪽 middleware가 action 객체를 인자로 하는 오른쪽 미들웨어가 return한 함수를 감싸는 형태가 나올 것이다.