題外話:年前最後一個工作日事情不多,正好把近期看過的但是容易忘的重要知識點總結一下,畢竟好記性不如爛筆頭,不再少年不扶不行。
先放一個Redux中文文檔關於Middleware(中介軟體)的官方解釋。
Redux Middleware
Middleware這個概念是Redux從其他架構借鑒過來的,本意如下:
middleware是指可以被嵌入在架構接收請求到產生響應過程之中的代碼。例如,Express 或者 Koa 的 middleware 可以完成添加 CORS headers、記錄日誌、內容壓縮等工作。
而在Redux中:
middleware被用於解決不同的問題,但其中的概念是類似的。它提供的是位於 action 被發起之後,到達 reducer 之前的擴充點。 你可以利用 Redux middleware 來進行日誌記錄、建立崩潰報告、調用非同步介面或者路由等等。
以上兩段引用均來自官方文檔。 可以看出Redux中的middleware和其他架構中要做的事情大同小異。本質上是切面編程 Aspect Oriented Programming(AOP)的一種思想。商務邏輯和其他部分(日誌,錯誤報表等)可以實現一個良好的解藕。從設計模式上來說可以視作裝飾者模式。
用法
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, initialState);
下面是applyMiddleware的源碼:
import compose from './compose'export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } let chain = [] const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}
下面是一個middleware樣本的代碼(來自官方文檔):
/** * 記錄所有被發起的 action 以及產生的新的 state。 */const logger = store => next => action => { console.group(action.type) console.info('dispatching', action) let result = next(action) console.log('next state', store.getState()) console.groupEnd(action.type) return result}
我在看原始碼的時候主要提了四個問題:
1. 為什麼要傳入createStore
2. …args是什麼
3. compose的作用
4. middleware這麼多函數嵌套,每次傳入的參數是什麼以及作用
通過對以上問題的一一解答,加深了我對applyMiddleware源碼的理解。
為什麼要傳入createStore
通過以上這一句可以看出就是為了單純的建立store。只不過是通過middleware把它包裹在洋蔥的最深處。
…args是什麼
我把args展開,可以看出它就是Redux原生createStore方法的標準參數,這也從另一方面回答了第一個問題。
compose的作用
compose本身並非Redux特有的概念,而是函數式編程的一種思想。關於函數式編程的入門可以參考阮大神的舊作:函數式編程初探。我在這裡只談compose.
compose顧英文名思中國義即組合函數,將多個函數組合起來串聯執行,一個函數的輸出作為另一個函數的輸入,一旦第一個函數開始執行,過程就會像多米諾骨牌。
註:當初對compose有個小小的疑惑。為什麼compose中的函數參數是從右向左執行。
根據以下代碼可以看出。看出啥我也很難描述,懂的自然懂(參數排列符合讀取順序,而執行順序恰好相反)。
let result = compose(f1, f2, f3, f4)(value);// 等價於let result = f1(f2(f3(f4(value))));
middleware這麼多函數嵌套,每次傳入的參數是什麼以及作用
將applyMiddleware和middleware樣本做一個對比:
從上圖可以得出:
1. middlewareAPI其實就是一個暴漏了getState和dispach方法的簡約版store。
2.在middleware中,next(action)是來派發action的,合理懷疑就是store的dispatch,通過對比applyMiddleware中的代碼可以證實這一點。
為了印象深刻,我的路徑是先假設有一個middleware,再擴充至多個:
一個middlwware
//源碼中dispatch = compose(...chain)(store.dispatch) //等價於fnMiddle = fn(middlewareAPI);dispatch = fnMiddle(store.dispatch)//等價於dispatch = fn(middlewareAPI)(store.dispatch)
//常用的store.dispatch(action) //等價於fn(middlewareAPI)(store.dispatch)(action)//正好對應middleware的三個參數store, next, action
多個middlwware: fn1, fn2
//源碼中dispatch = compose(...chain)(store.dispatch) //等價於dispatch= fn1Middle(fn2Middle(store.dispatch))// fn1Middle = fn1(middlewareAPI) etc..
讀到這裡就有點意思了, 首先執行的是fn2Middle(store.dispatch),返回結果是如下的一個函數。緊接著fn1Middle( (action) => {} ),這個action函數相當於佔據了fn1中next參數的位置。在收到action參數之前呢函數並不會執行。那麼在收到acton參數之後呢,繼續用語言描述實在是太難了,我試著畫了一個圖,希望在回看時不要再燒很多腦。
// fn2Middle(store.dispatch)執行後(action) => { .... next(action) ....}
再補充一點:
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, initialState);
插入中介軟體後形成了newStore。此時newStore.dispatch方法不再是原生的redux store.dispatch了,而是依次放入了中介軟體的邏輯,原生store.dispatch放入了洋蔥模型的最裡面。
dispatch= fn1Middle(fn2Middle(store.dispatch))
上述實現的關鍵在於applyMiddleware源碼最後的return值,這個ES6風格的寫法的結果就是後加入的dispatch方法覆蓋了原生store.dispatch
export default function applyMiddleware(...middlewares) { return createStore => (...args) => { .... dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}
重新梳理一遍清楚多了,這麼燒腦目測過半個月准忘,記錄的意義就像redux-devtool中的時光旅行一樣,在紛紛擾擾的操作之後還可以找回這片初心。
祝自己以及所有人春節快樂。