Redux 的中間件提供的是位於 action
被髮起以後,到達 reducer
以前的擴展點,換而言之,本來 view -> action -> reducer -> store
的數據流加上中間件後變成了 view -> action -> middleware -> reducer -> store
,在這一環節咱們能夠作一些 「反作用」 的操做,如 異步請求、打印日誌等。javascript
以日誌輸出 Logger 爲例:java
import { createStore, applyMiddleware } from 'redux'
/** 定義初始 state**/
const initState = {
score : 0.5
}
/** 定義 reducer**/
const reducer = (state, action) => {
switch (action.type) {
case 'CHANGE_SCORE':
return { ...state, score:action.score }
default:
break
}
}
/** 定義中間件 **/
const logger = ({ getState, dispatch }) => next => action => {
console.log('【logger】即將執行:', action)
// 調用 middleware 鏈中下一個 middleware 的 dispatch。
let returnValue = next(action)
console.log('【logger】執行完成後 state:', getState())
return returnValue
}
/** 建立 store**/
let store = createStore(reducer, initState, applyMiddleware(logger))
/** 如今嘗試發送一個 action**/
store.dispatch({
type: 'CHANGE_SCORE',
score: 0.8
})
/** 打印:**/
// 【logger】即將執行: { type: 'CHANGE_SCORE', score: 0.8 }
// 【logger】執行完成後 state: { score: 0.8 }複製代碼
要理解上面這段代碼,首先要從建立store
的createStore
函數提及:createStore
函數接收參數爲(reducer, [preloadedState], enhancer)
,其中preloadedState
爲初始state
,那麼 enhancer
又是什麼呢?從官方文檔能夠看到,StoreCreator
的函數簽名爲redux
type StoreCreator = (reducer: Reducer, initialState: ?State) => Store複製代碼
是一個普通的建立 store
的函數,而 enhancer
的簽名爲數組
type enhancer = (next: StoreCreator) => StoreCreator複製代碼
可知enhancer
是一個組合 StoreCreator
的高階函數
, 返回的是一個新的強化過的 StoreCreator
,再執行StoreCreator
就能獲得一個增強版的 store。
在本例裏形參enhancer
即爲applyMiddleware
,從下面的源碼可知,applyMiddleware
改寫了 store
的 dispatch
方法,新的 dispatch
便是被所傳入的中間件包裝過的。bash
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
// 接收 createStore 參數
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
// 傳遞給中間件的參數
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 註冊中間件調用鏈,並由此可知,全部的中間件最外層函數接收的參數都是{getState,dispatch}
chain = middlewares.map(middleware => middleware(middlewareAPI))
//compose 函數起到代碼組合的做用:compose(f, g, h)(...args) 效果等同於 f(g(h(...args))),具體實現可參見附錄。今後也可見:全部的中間件最二層函數接收的參數爲 dispatch,通常咱們在定義中間件時這個形參不叫 dispatch 而叫 next,是因爲此時的 dispatch 不必定是原始 store.dispatch,有多是被包裝過的新的 dispatch。
dispatch = compose(...chain)(store.dispatch)
// 返回經 middlewares 加強後的 createStore
return {
...store,
dispatch
}
}
}複製代碼
這樣下來,原來執行 dispatch(action)
的地方變成了執行新函數app
(action)=>{
console.log('【logger】即將執行:', action)
dispatch(action)
console.log('【logger】執行完成後 state:', getState())
}複製代碼
這樣就實現了action -> reducer
的攔截,因此每次觸發 action
都能被 log 出來了,😄。異步
對於異步中間件的狀況也同理 , 以 redux-thunk
爲例:函數
// 這是簡化後的 redux-thunk
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};複製代碼
這裏能夠看到,當 dispatch
的收到的 action
爲函數時,將試圖嵌套執行這個函數。套用這個中間件後的 dispatch
方法就更 「聰明」 了,這就是爲何 redux
中規定 action
必須爲純對象而在 redux-thunk
中傳的 action
倒是 function
而不會報錯的緣由。ui
redux 中間件經過改寫 store.dispatch 方法實現了action -> reducer
的攔截,從上面的描述中能夠更加清晰地理解 redux 中間件的洋蔥圈模型
:this
中間件A -> 中間件B-> 中間件C-> 原始 dispatch -> 中間件C -> 中間件B -> 中間件A複製代碼
這也就提醒咱們使用中間件時須要注意這個中間件是在何時 「搞事情」 的,好比 redux-thunk
在執行 next(action)
前就攔截了類型爲 function
的 action
,而 redux-saga
就在 next(action)
纔會觸發監聽 sagaEmitter.emit(action)
, 並不會攔截已有 action
到達 reducer。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}複製代碼
精妙之處就在巧妙的利用了 Array.prototype.reduceRight(callback[, initialValue])
這個咱們平時不怎麼用到的函數。該方法將數組中每一項從右向左調用callback
,本例中的callback
即爲
(composed, f) => f(composed)複製代碼
initialValue
初始值是數組中最後一個func。
這裏下面是另外一種實現:
const compose = (...funcs) => (result) => {
//... 省略邊界判斷
for (var i = funcs.length - 1; i > -1; i--) {
result = funcs[i].call(this, result);
}
return result;
}複製代碼
這種寫法就更容易理解爲何compose(f, g, h)(...args)
效果等同於 f(g(h(...args)))
,可是就沒有上面那種優雅😂。