翻看Redux的源碼,能夠發現,它主要輸出createStore, combineReducers, bindActionCreators, applyMiddleware, compose 五個接口,
首先,讓咱們先看看middleware是個啥html
Redux裏咱們都知道dispatch一個action,就會到達reducer,而middleware就是容許咱們在dispatch action以後,到達reducer以前,搞點事情。react
好比:打印,報錯,跟異步API通訊等等git
下面,讓咱們一步步來理解下middle是如何實現的:es6
假設咱們有個需求,想打印出dispatch的action以後,nextState的值。github
如圖:express
先用一種很簡單的方式去實現redux
let action = toggleTodo('2'); console.log('dispatch', action); store.dispatch(action); console.log('next state', store.getState())
包裹進函數裏 function dispatchAndLoge (store, action) { let action = toggleTodo('2'); console.log('dispatch', action); store.dispatch(action); console.log('next state', store.getState()) }
可是咱們並不想每次要用的時候都須要import這個函數進來,因此咱們須要直接替代。數組
直接替代store的dispatch,在原先的dispatch先後加上咱們的代碼,把原先的store.dispatch放進next裏。app
const next = store.dispatch; store.dispatch = function (action) { let action = toggleTodo('2'); console.log('dispatch', action); next(action) console.log('next state', store.getState()) }
這樣下一次咱們在用dispatch的時候就自動附帶了log的功能,這裏的每個功能,咱們都稱之爲一個middleware,用來加強dispatch的做用。異步
接下來咱們就須要思考,如何能夠鏈接多個middleware。
先把報錯的middleware包裝成函數寫上來
function patchStoreToAddLogging(store) { let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); let result = next(action); console.log('next state', store.getState()); return result; } } function patchStoreToAddCrashReporting(store) { let next = store.dispatch; store.dispatch = function dispatchAndReportErrors(action) { try { return next(action); } catch (err) { console.error('Caught an exception!', err); Raven.captureException(err, { extra: { action, state: store.getState(); } }) throw err; } } }
這裏咱們用替代了store.dispatch,其實咱們能夠直接return 這個函數,就能夠在後面實現一個鏈式調用,賦值這件事就在applyMiddleware裏作。
function logger(store) { let next = store.dispatch // Previously: // store.dispatch = function dispatchAndLog(action) { return function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } }
這裏咱們提供了一個applyMiddleware的方法,能夠將這兩個middleware連起來
它主要作一件事:
將上一次返回的函數賦值給store.dispatch
function applyMiddlewareByMonkeypatching(store, middlewares) { middlewares = middlewares.slice() middlewares.reverse() // Transform dispatch function with each middleware. middlewares.forEach(middleware => // 因爲每次middle會直接返回返回函數,而後在這裏賦值給store.dispatch, 、、 下一個middle在一開始的時候,就能夠經過store.dispatch拿到上一個dispatch函數 store.dispatch = middleware(store) ) }
接下來,咱們就能夠這樣用
applyMiddlewareByMonkeypatching(store, [logger, crashReporter])
剛剛說過,在applyMiddle裏必需要給store.dispatch賦值,不然下一個middleware就拿不到最新的dispatch。
可是有別的方式,那就是在middleware裏不直接從store.dipatch裏讀取next函數,而是將next做爲一個參數傳入,在applyMiddleware裏用的時候把這個參數傳下去。
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 } } }
用es6的柯西化寫法,能夠寫成下面的形式,其實這個next我我的以爲叫previous更爲合適,由於它指代的是上一個store.dispatch函數。
const logger = store => next => action => { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } 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 } }
由此咱們能夠看到因此middleware要傳入的參數就是三個,store,next,action。
接下來,再看看redux-thunk 的源碼,咱們知道,它用於異步API,由於異步API action creator返回的是一個funciton,而不是一個對象,因此redux-thunk作的事情其實很簡單,就是看第三個參數action是不是function,是的話,就執行它,若是不是,就按照原來那樣執行next(action)
function createThunkMiddleware(extraArgument) { return (store) => next => action => { if(typeof action === 'function') { return action(dispatch, getState, extraArgument) } return next(action) } } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
接着,在applyMiddleware裏有能夠不用馬上對store.dispatch賦值啦,能夠直接賦值給一個變量dispatch,做爲middleware的參數傳遞下去,這樣就能鏈式的加強dispatch的功能啦~
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}) }
下面終於能夠看看applyMiddleware的樣子啦
/** * Composes single-argument functions from right to left. The rightmost * function can take multiple arguments as it provides the signature for * the resulting composite function. * * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions * from right to left. For example, compose(f, g, h) is identical to doing * (...args) => f(g(h(...args))). */ export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) } //能夠看出compose作的事情就是上一個函數的返回結果做爲下一個函數的參數傳入。 /** * Creates a store enhancer that applies middleware to the dispatch method * of the Redux store. This is handy for a variety of tasks, such as expressing * asynchronous actions in a concise manner, or logging every action payload. * * See `redux-thunk` package as an example of the Redux middleware. * * Because middleware is potentially asynchronous, this should be the first * store enhancer in the composition chain. * * Note that each middleware will be given the `dispatch` and `getState` functions * as named arguments. * * @param {...Function} middlewares The middleware chain to be applied. * @returns {Function} A store enhancer applying the middleware. */ export default function applyMiddleware(...middlewares) { //能夠這麼作是由於在creatStore裏,當發現enhancer是一個函數的時候 // 會直接return enhancer(createStore)(reducer, preloadedState) return (createStore) => (...args) => { // 以後就在這裏先創建一個store const store = createStore(...args) let dispatch = store.dispatch let chain = [] // 將getState 跟dispatch函數暴露出去 const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } //這邊返回chain的一個數組,裏面裝的是wrapDispatchToAddLogging那一層,至關於先給 middle剝了一層皮,也就是說 // 接下來只須要開始傳入dispatch就行 chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) // wrapCrashReport(wrapDispatchToAddLogging(store.dispatch)) // 此時返回了上一個dispatch的函數做爲wrapCrashReport的next參數 // wrapCrashReport(dispatchAndLog) // 最後返回最終的dipatch return { ...store, dispatch } } }
總結,其實每個middleware都在加強dispatch的功能,在dispatch action的先後搞點事情~
參考文檔:
http://redux.js.org/docs/adva...
https://github.com/reactjs/re...
https://github.com/reactjs/re...
https://github.com/reactjs/re...