中間件的內容其實不屬於React
源碼相關,屬於Redux
相關。可是中間件的原理是一個很是重要的知識點,它是咱們前端開發解決一些業務問題時的利器。不少前端應用的架構都是使用中間件爲基礎搭建的。前端
本文不會介紹Redux
相關的內容,只關注於中間件的實現原理。react
本文將是我學習react
源碼的目前階段的最後一篇文章,react
源碼內容比較多,也比較晦澀,我也不可以一蹴而就。等過段時間再繼續深刻學習的時候,再來接着更新這一系列的內容。git
在React
開發中,管理數據狀態的Redux
是每一個人都會接觸到的內容(如同Vuex
在Vue
開發當中的地位)。咱們知道,在Redux
中,咱們想要修改數據,咱們必須先派發一個Action
,Action
會被Reducer
讀取,Reducer
將根據Action
內容的不一樣執行不一樣的計算邏輯,最終生成新的state
,這個新的state
會更新到Store
對象裏,進而驅動視圖層面做出對應的改變。github
這裏有一個須要注意的地方,Redux
源碼中只有同步操做,也就是說當咱們dispatch action
時,state
會被當即更新。編程
若是咱們想引入異步數據流,該怎麼辦?官方的建議就是使用中間件。redux
本文使用redux-thunk
做爲處理異步數據流的方式。源碼地址數組
import thunkMiddleware from 'redux-thunk'
import reducer from './reducers'
// 使用redux-thunk中間件
const store = createStore(reducer, applyMiddleware(thunkMiddleware))
複製代碼
這樣配置以後,咱們就能夠給dispatch
(這個dispatch
並不是原始的dispatch
)傳入一個函數,並能夠在該函數中使用異步數據流。markdown
thunk
的源碼很簡單,也就10多行代碼架構
function createThunkMiddleware(extraArgument) {
// 返回值是一個 thunk,它是一個函數
return ({ dispatch, getState }) => (next) => (action) => {
// thunk 若感知到 action 是一個函數,就會執行 action
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 若 action 不是一個函數,則不處理,直接放過
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
複製代碼
在這裏先簡單的分析一下它的源碼。首先這個方法返回是一個三重的函數,第一重接收的是一個dispatch
(組合中間件出來的一個鏈)和getState
(store.getState
獲取state
數據的方法)的對象,第二重是next
(store.dispatch
最原始的dispatch
方法),第三重是action
(這個方法是一個action
對象或者是一個異步的函數)。app
這裏的內容可能一會兒沒法理解,不要緊,接下來咱們配合Redux
的applyMiddleware
的方法流程,一步步來剖析Redux
的中間件原理。
咱們先看看applyMiddleware
的源碼。
// applyMiddlerware 會使用「...」運算符將入參收斂爲一個數組
export default function applyMiddleware(...middlewares) {
// 它返回的是一個接收 createStore 爲入參的函數
return createStore => (...args) => {
// 首先調用 createStore,建立一個 store
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.`
)
}
// middlewareAPI 是中間件的入參
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 遍歷中間件數組,調用每一箇中間件,而且傳入 middlewareAPI 做爲入參,獲得目標函數數組 chain
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 改寫原有的 dispatch:將 chain 中的函數按照順序「組合」起來,調用最終組合出來的函數,傳入 dispatch 做爲入參
dispatch = compose(...chain)(store.dispatch)
// 返回一個新的 store 對象,這個 store 對象的 dispatch 已經被改寫過了
return {
...store,
dispatch
}
}
}
複製代碼
由於applyMiddleware
是在createStore
當中使用的,因此咱們也須要看一部分的createStore
源碼。
function createStore(reducer, preloadedState, enhancer) {
// 這裏處理的是沒有設定初始狀態的狀況,也就是第一個參數和第二個參數都傳 function 的狀況
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 此時第二個參數會被認爲是 enhancer(中間件)
enhancer = preloadedState;
preloadedState = undefined;
}
// 當 enhancer 不爲空時,便會將原來的 createStore 做爲參數傳入到 enhancer 中
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState);
}
......
}
複製代碼
createStore
對於applyMiddleware
的使用邏輯比較簡單其實主要就是一句話
return enhancer(createStore)(reducer, preloadedState);
複製代碼
咱們把這幾個參數,代入applyMiddleware
中再去看。
// middlewares是傳入的中間件
export default function applyMiddleware(...middlewares) {
// 它返回的是一個接收 createStore 爲入參的函數
return createStore => (...args) => {
......
}
}
複製代碼
applyMiddleware
方法中的createStore
,其實就是Redux
中的createStore
方法,而args
則對應的是reducer
、preloadedState
,這兩個參數均爲createStore
函數的約定入參。
咱們接着來看下面的內容
// 首先調用 createStore,建立一個 store
const store = createStore(...args)
// 用來防止在遍歷 middleware 時調用dispatch
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// middlewareAPI 是中間件的入參
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 遍歷中間件數組,調用每一箇中間件,而且傳入 middlewareAPI 做爲入參,獲得目標函數數組 chain
const chain = middlewares.map(middleware => middleware(middlewareAPI))
複製代碼
這裏的內容就是調用createStore
建立一個store
。而後建立一個middlewareAPI
對象,遍歷middleware
(中間件),並傳入middlewareAPI
參數。這裏其實就是以前thunk
第一重函數接收的dispatch
和getState
。
而後看下一句
dispatch = compose(...chain)(store.dispatch)
複製代碼
這裏的compose(...chain)
咱們下面的章節再看,這裏能夠先把這個方法當作以下
dispatch = middleware(middlewareAPI)(store.dispatch)
複製代碼
這裏正好對於thunk
的第二重函數接收next
參數。這裏要注意一點,dispatch
已經被重寫。
return {
...store,
dispatch
}
複製代碼
最後返回一個重寫過dispatch
的對象,這個對象會被綁定到咱們的頁面上,好比
<Provider store={store}>
<App />
</Provider>
複製代碼
而後咱們經過useDispatch
拿到的dispatch
其實就是thunk
中間件改寫過的方法。咱們用這個dispatch
去傳入action
對象或者異步數據流函數,其實就是調用thunk
的第三重函數
(action) => {
// thunk 若感知到 action 是一個函數,就會執行 action
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 若 action 不是一個函數,則直接使用dispatch派發action
return next(action);
};
複製代碼
注意一點,這裏的dispatch
已是被改寫的dispatch = compose(...chain)(store.dispatch)
。
這樣,咱們就經過中間件實現了Redux
的異步數據流。咱們就能夠給dispatch
傳入一個函數,來異步的派發action
。
compose
是一個函數式編程中,很經常使用的工具方法。它的功能就是把多個函數組合起來。
在redux
中如有多箇中間件,那麼redux
會結合它們被「安裝」的前後順序,依序調用這些中間件。因此,咱們須要使用compose
方法把中間件函數組合起來。
注意:compose
只是一個函數式編程的思路,實現方式有不少種,下面只是redux
中實現的一種
port default function compose(...funcs) {
// 處理數組爲空的邊界狀況
if (funcs.length === 0) {
return arg => arg
}
// 若只有一個函數,也就談不上組合,直接返回
if (funcs.length === 1) {
return funcs[0]
}
// 如有多個函數,那麼調用 reduce 方法來實現函數的組合
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
compose(f1, f2, f3, f4);
// => 會轉換成下面這種形式
(...args) => f1(f2(f3(f4(...args))));
複製代碼
多箇中間件嵌套的時候,這裏的思路可能會有點繞,我舉個例子來講明一下。假設咱們使用了兩個中間件,一個是thunk
,一個是redux-logger
(當redux
數據變動的時候,會打印相關信息到控制檯)。源碼地址
// dispatch定義
dispatch = thunk(createLogger(store.dispatch));
// dispatch被使用
dispatch(action);
//等同於
thunk(createLogger(store.dispatch))(action)
複製代碼
咱們來看下redux-logger
的源碼,若是我只保留與redux
中間件相關的邏輯的話,它的源碼能夠壓縮成幾行代碼。
return ({ getState }) => next => (action) => {
return next(action);
};
複製代碼
這裏也有三重的代碼,第一重在咱們遍歷中間件數組的時候就被調用了
const chain = middlewares.map(middleware => middleware(middlewareAPI));
複製代碼
因此這裏傳入thunk
的應該是第二重的代碼
thunk(createLogger(store.dispatch))(action)
// 等同於
thunk((action) => store.dispatch(action))(action)
複製代碼
咱們再來看看以前thunk
中間件的源碼
function createThunkMiddleware(extraArgument) {
// 返回值是一個 thunk,它是一個函數
return ({ dispatch, getState }) => (next) => (action) => {
// thunk 若感知到 action 是一個函數,就會執行 action
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 若 action 不是一個函數,則不處理,直接放過
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
複製代碼
咱們再次來精簡代碼
thunk((action) => store.dispatch(action))(action)
// 等同於
const thunk = (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
thunk((action) => store.dispatch(action))(action);
// 等同於
const thunk = (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return store.dispatch(action);
};
thunk(action);
複製代碼
這樣,就實現了多箇中間件的依次調用。
若是本文對你有所幫助,請幫忙點個贊,感謝!