redux中間鍵的作用
在redux的流程中,會存在一些特殊的需求,比如列印action的資訊來進行除錯,或者要處理一個非同步請求。中間鍵就是為了處理這些特殊的需求而存在的。在redux的流程中,action creator 和 reducer 都是純函式,action creator返回一個包含type和payload的物件,reducer函式用來處理和返回state,應該保證他們的單一性和純度。所以能夠處理這些特殊需求的只剩下dispatch了,redux的中間鍵就是用來對dispatch進行增強的。
一個簡單的初始化
import { createStore, applyMiddleware } from `redux`;
import thunk from `redux-thunk`;
import promise from `redux-promise`;
import logger from `redux-logger`;
import rootReducer from `../reducers`;
export default function configureStore() {
return createStore(
rootReducer,
applyMiddleware(thunk, promise, logger),
);
}複製程式碼
在使用createStore進行初始化的時候,如果第三個引數不為空,或者第二引數為一個函式,那麼就用這個enhancer函式進行初始化。createStore會被當成enhancer函式的引數傳入,並在enhancer這個函式再次使用createStore對redux進行初始化。
applyMiddleware.js
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.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}複製程式碼
createStore是初始化傳進來的,…args是初始化用的reducers和preloadedState,最終生成store物件。
在函式內部定義一個dispatch函式,定義一個middlewareAPI物件,將原生的getState和自定義的dispatch賦值給它。
middlewares.map將middlewareAPI這個物件送到每個中間鍵中,使得每個中間鍵都能通過getState訪問到整個store上的資料,並且通過閉包將自定義的dispatch函式傳到每個中間鍵中,使得最終加強過的dispatch函式能被每個中間鍵呼叫。
compose函式用來對中間鍵陣列chain進行處理,生成最終的dispatch函式。
compose.js
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函式,通過陣列的reduce函式,將中間鍵進行合併。
compose([f, g, h]) ===> (…args) => f(g(h(…args)))
compose函式可以簡化為上面的函式,在本文中,引入了三個中間鍵,下面將用這三個中間鍵進行簡單的說明。
const thunk = thunk(middlewareAPI);
const promise = promise(middlewareAPI);
const logger = logger(middlewareAPI);
const chain = [thunk, promise, logger];
compose(...chain)
↓ ↓ ↓ ↓ ↓ ↓
(...args) => thunk(promise(logger(...args)))
中間鍵中的next引數,用來將action傳遞到下一個中間鍵中,通過上面的轉換可以看出來:
thunk函式中的next等於promise(logger(..args))
promise函式中的next等於logger(...args)
最終生成的dispatch函式可以表示為:
dispatch = compose(...chain)(store.dispatch)
↓ ↓ ↓ ↓ ↓ ↓
dispatch = thunk(promise(logger(store.dispatch)))
因為閉包的原因,最終生成的dispatch函式將會傳入到每個中間鍵中。複製程式碼
通過上面的分析,可以看出每一個middleware中store的dispatch函式通過閉包的作用,都和compose函式最終生成的dispatch保持一致,並且原生的dispatch函式被包裹在最內層,middleware通過呼叫next()函式進入下一個中間鍵,並最終呼叫原生的dispatch函式實現action的分發,具體的過程如下圖
如果在某一箇中間鍵中呼叫了store.dispatch,那麼就相當於從當前的中間鍵中跳出,重新執行一遍上圖中的過程。如果中間鍵一直粗暴的執行store.dispatch而沒有一個合適的狀態去呼叫next函式,那麼就會進入一個死迴圈中。具體的過程如下圖
以reudx-promise為例
import isPromise from `is-promise`;
import { isFSA } from `flux-standard-action`;
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action) ? action.then(dispatch) : next(action);
}
return isPromise(action.payload)
? action.payload
.then(result => dispatch({ ...action, payload: result }))
.catch(error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
})
: next(action);
};
}複製程式碼
在redux-promise中,如果action的payload是一個promise物件,那麼會在then中呼叫dispatch方法,此時就會跳出promise這個中間鍵,從最外層重新進入這個中間鍵鏈;如果action的payload是一個正常的物件,那麼會執行next函式,進入下一個中間鍵。