前言
在前面兩篇文章中,我們分別介紹了Redux的工作機制及閱讀了它的核心原始碼,但其實還少了一部分比較重要的內容沒有介紹,就是Redux的middleware機制。這篇文章我們就來通過讀原始碼的方式來看一下Redux middleware的工作機制。
Middleware是什麼?
寫過node的同學大都接觸過middleware的概念,比如express的後端框架都有middleware。那麼到什麼是一個middleware呢?從字面意思上理解,middleware是“中介軟體”的意思。我的理解是,一個框架的middleware,就是指這個框架允許我們在某個流程的執行中間插入我們自定義的一段程式碼。官網的說法是:
It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
和express類似,Redux的middleware也有幾個特性:
關於執行時機。middleware的程式碼執行時機是由框架確定的,我們只能定義程式碼,但無法改變程式碼執行的時機。比如express的middleware是在一個請求到達和服務端的響應產生之間呼叫。而Redux的middleware是在dispatch一個action,和action到達reducer之間呼叫。middleware允許鏈式呼叫。意思是我們可以註冊多個middleware,框架會按照順序依次呼叫我們的middleware。如果我們註冊了middleware,那麼我們在之後每次dispatch的時候都會把所有middleware都走一遍。如何使用?
學習一項技術最好的方式就是使用它。很多時候我們聽了很多理論,看了很多遍原始碼,但還是不知道它在幹什麼。這個時候我們如果可以看一下作為使用者是如何使用這項技術的,對理解它為什麼存在以及為什麼那樣實現會非常有幫助。Redux的middleware就是這樣的技術,如果你直接去讀原始碼,其實程式碼量很少,但就是不知道它在幹嘛。如果去看幾個實際的應用案例,對理解的幫助會非常大。我們首先來看一下,如果我們要註冊一個middleware,程式碼應該如何寫呢?虛擬碼如下:
import { compose, createStore, applyMiddleware } from 'redux';// 假如我們要註冊兩個middleware: middleware1和middleware2const enhancer = applyMiddleware(middleware1, middleware2);const store = createStore(reducers, enhancer);
在以上程式碼中,我們在呼叫createStore建立store的時候註冊了兩個middleware。我們呼叫了redux暴露出來的applyMiddleware函式,它返回一種成為storeEnhancer的函式。然後我們將這個enhancer傳給createStore。這樣createStore會被修改,經過這樣返回的store將是被插入了middleware程式碼的。
原理
以上我們知道了如何在createStore的時候使用一些已有的middlewares來實現我們的需求。那麼Redux的middleware機制到底是如何執行的呢?這才是我們這篇文章想要搞懂的重點。接下來我們就一一來看一下:
createStore函式的enhancer引數,我們看一下createStore的原始碼(經過刪減,重點關注它對於enhancer引數的處理):export default function createStore(reducer, enhancer) {\tif (enhancer) { \treturn enhancer(createStore)(reducer); } return { \t// return created store }}
可以看到,如果我們傳了enhancer函式給createStore的話,那麼它會呼叫enhancer,並傳入原始的createStore函式作為引數。而我們這裡的enhancer就是applyMiddleware函式,那麼接下來我們看一下applyMiddleware函式的實現方式。
applyMiddleware的實現,為了看清楚函式呼叫的層級關係,我將程式碼中的箭頭函式都改為了function的寫法,並給每個函式都起了名字,我個人覺得這樣更容易理解函式之間的呼叫關係。如果你覺得箭頭函式更容易懂也可以直接看Redux的原始碼。export default function applyMiddleware(...middlewares) { // applyMiddleware返回另一個函式,也就是`enhancer`。 // `enhancer`接受原來的createStore函式為引數.\treturn function enhancer(createStore) { // enhancer的返回值是另一個函式,其實就是`新的createStore` \treturn function enhancedCreateStore(...args) { // 呼叫老的createStore,來獲得store。 \tconst store = createStore(...args) // 定義新的dispatch函式,後邊會被修改 let dispatch = () => { \tthrow new Error('Dispatching while constructing your middleware is not allowed.Other middleware would not be applied to this dispatch.') } // 暴露個每個middleware的API。 const middlewareAPI = { \tgetState: store.getState, dispatch: (...args) => dispatch(...args) } // 把所有傳入的middlewares轉為一個數組。 const chain = middlewares.map(function(middleware) { \treturn middleware(middlewareAPI) }) // 新的dispatch函式,其實就是把所有middleware的呼叫鏈加入dispatch的執行過程中。 dispatch = compose(...chain)(store.dispatch) // 新的createStore的返回值,其實唯一變化的就是dispatch欄位。 return { \t...store, dispatch, } } }}
以上我們詳細解釋了applyMiddleware每一行程式碼的執行過程。可以看到,所謂執行middleware,其實也就是將原來的dispatch函式替換為會遍歷執行所有middleware的新的dispatch函式。那麼在執行過enhancer後,在我們的程式碼中拿到的store.dispatch函式就是經過修改的新的dispatch函數了,結果就是我們每次呼叫dispatch都會走一遍所有middlewares。
上面我們知道了所有middlewares函式會被連結到一起然後順序執行,那麼這具體是如何做到的呢?我們需要看一下compose函式的實現:
compose函式:export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce(function reducer(a, b) { return function (...args) { return a(b(...args)) } })}
前兩個if條件主要用於處理一些邊界條件,不是主要內容,可以先忽略。函式的主體就是funcs.reduce的部分。所以要理解這個函式,就必須理解array.reduce函式。根據文件:
The reduce() method executes a reducer function (that you provide) on each member of the array resulting in a single output value.
它的執行過程就是對funcs陣列中的每個元素呼叫reducer。那麼我們接下來看一下reducer的兩個引數a和b分別代表什麼呢?在reducer函式中:
第一個引數表示accumulator,它的值是上一次呼叫reducer的返回值第二個引數表示currentValue,表示陣列當前遍歷到的元素所以,b就是當前正在遍歷的函式,a表示上一次呼叫結果,也就是每一次迭代到下一個元素a就會被更新為上次呼叫的結果。
現在我們用一個示例來測試一下compose的執行過程,假設我們有三個函式,分別如下:
import { compose } from 'redux'function fun1(n) { console.log('fun1: ', n) return 1}function fun2(n) { console.log('fun2: ', n) return 2}function fun3(n1, n2, n3) { console.log('fun3: ', n1, n2, n3) return 3}const fun = compose(fun1, fun2, fun3)fun('a', 'b', 'c')
執行上面的程式碼,將得到以下輸出:
fun3: a b cfun2: 3fun1: 2
也就是說,compose接受N個函式作為輸入引數,然後將這些函式依次從右向左執行,並把每個函式的返回值作為引數傳入下一個被呼叫的函式中,也就是把所有函式的呼叫"串聯"了起來。也就是相當於這樣寫:
fun1(fun2(fun3('a', 'b', 'c')))
需要注意的是,除了最右的函式外,其他函式都只接受一個引數(也就是上一個函式的返回值)。而最右側的函式是可以接受任意引數的,因為它是作為整個compose返回函式的簽名(它的引數是外部呼叫的時候傳入的)。
整體流程
上面主要介紹了applyMiddleware的過程,這一步通常是在程式啟動的時候執行一次。那麼接下來我們看一下middleware的整體執行過程,假設我們有以下middlewares:
const logger = middlewareAPI => next => action => {\tconsole.log('start dispatch: ', action) let result = next(action) console.log('next state: ', store.getState()) return result}const errorHandler = middlewareAPI => next => action => { try { \treturn next(action) } catch (err) { \t// handle error }}// dummy reducerfunction reducer() {}const store = createStore(reducer, applyMiddleware(errorHandler, logger))store.dispatch({ type: 'test' })
以上我們定義了兩個middleware,分別是logger和errorHandler。程式碼執行流程如下:
執行applyMiddleware,首先會呼叫每個middleware函式,然後將middlewareAPI作為引數傳入。middleware返回一個函式,這裡才是真正的middleware的執行體,也就是:next => action => {}的部分。然後程式碼會通過compose將所有middleware組成一條呼叫鏈,依次執行。這裡有個問題需要注意一下,就是原始的dispatch在什麼時候執行?根據applyMiddleware.js中的程式碼:
compose(...chain)(store.dispatch)
在我們上面的例子中,這句程式碼相當於:
errorHandler(logger(store.dispatch))
這句程式碼的執行順序是:首先執行logger,然後遇到next(action)的程式碼,接著執行errorHandler,最後執行原始的store.dispatch。
執行createStore,由於我們傳遞了enhancer引數,會返回帶有中介軟體的store(替換dispatch函式)。我們在程式碼中呼叫store.dispatch,這時拿到的dispatch函式是被修改過的,串聯了middleware的。所以會按照第一步中所描述的過程,依次執行所有middleware,最後執行原始的store.dispatch函式。總結
在本篇文章中,我們分別介紹了Redux middleware是什麼、我們應該如何使用、以及通過閱讀原始碼的方式對middleware的註冊和執行過程進行了詳細的分析。希望這篇文章可以幫助你對Redux的理解更加深刻~ 至此,我們通過三篇文章已經對Redux進行了全方面的分析和解讀,希望可以帶給你幫助~