上一講實現了react-redux,從而能夠更加優雅地在react中使用redux。javascript
可是,一個關鍵問題沒有解決:異步操做怎麼辦?Action 發出之後,Reducer 當即算出 State,這叫作同步;Action 發出之後,過一段時間再執行 Reducer,這就是異步。vue
怎麼才能 Reducer 在異步操做結束後自動執行呢?這就要用到新的工具:中間件(middleware)。java
爲了理解中間件,讓咱們站在框架做者的角度思考問題:若是要添加功能,你會在哪一個環節添加?react
只有發送 Action 的這個步驟,即store.dispatch()方法,能夠添加功能。舉例來講,要添加日誌功能,把 Action 和 State 打印出來,能夠對store.dispatch進行以下改造。git
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action);
next(action);
console.log('next state', store.getState());
}
複製代碼
對store.dispatch
進行了重定義,在發送 Action 先後添加了打印功能。這就是中間件的雛形。 中間件就是一個函數,對store.dispatch
方法進行了改造,在發出 Action 和執行 Reducer 這兩步之間,添加了其餘功能。github
在編寫中間件以前,咱們先看下在真正的redux裏面是如何使用中間件的,當使用單箇中間件時代碼以下,其中thunk就是一箇中間件:redux
const store = createStore(counter, applyMiddleware(thunk))
複製代碼
有上面的代碼能夠看出,咱們須要作三件事情bash
第一步 對createStore函數進行擴展,使其可以接收第二個參數——中間件app
第二步 定義applyMiddleware函數,使其可以將一箇中間件加入到redux中框架
第三步 實現一箇中間件——redux-thunk
代碼以下,檢測是否有加強器,若存在則先用加強器對createStore進行擴展加強
export function createStore(reducer, enhancer) {
// 若是存在加強器,則先用加強器對createStore進行擴展加強
if (enhancer) {
return enhancer(createStore)(reducer)
}
...
}
複製代碼
根據上面的代碼,咱們能夠知道,applyMiddleware(中間件)
返回的是一個高階函數,接收參數createStore後,返回一個函數,而後再接收參數reducer。 所以對應代碼以下:
export function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 第一步 得到原生store以及原生dispatch
const store = createStore(...args)
let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 第二步 將原生dipatch傳入中間件進行擴展加強,生成新的dispatch
dispatch = middleware(midApi)(dispatch)
return {
...store, // 原生store
dispatch, // 加強擴展後的dispatch
}
}
}
複製代碼
在上述代碼中,咱們先是得到原生store以及原生dispatch,組成midApi,即中間件API,而後將其傳入中間件,執行中間件內定義的操做,返回一個函數,再傳入原生dispatch,再返回一個加強後的dispatch,最後傳入action。加強後的dispatch以下:
dispatch(action) = middleware(midApi)(store.dispatch)(action)
複製代碼
異步操做至少要送出兩個 Action:用戶觸發第一個 Action,這個跟同步操做同樣,沒有問題;如何才能在操做結束時,系統自動送出第二個 Action 呢?
奧妙就在 Action Creator 之中。
// action creator
export function addGun() {
return {type: ADD_GUN}
}
export function addGunAsync() {
return dispatch => {
setTimeout(() => {
dispatch(addGun())
}, 2000)
}
}
複製代碼
上文中有兩個需求,第一個需求是store.dispatch
一個action對象(即{type: ADD_GUN}),而後當即加機槍,即:
addGun = () => store.dispatch(addGun())
addGun()
複製代碼
第二個需求是store.dispatch
一個函數,這個函數內部執行異步操做,在2000ms以後再執行store.dispatch(addGun())
,加機槍,可是store.dispatch
參數只能是action這樣的對象,而不能是函數。store.dispatch
的有關源碼以下:
function dispatch(action) {
// reducer根據老的state和action計算新的state
currentState = reducer(currentState, action)
// 當全局狀態變化時,執行傳入的監聽函數
currentListeners.forEach(v => v())
return action
}
複製代碼
爲了可以讓讓store.dispatch可以接收函數,咱們可使用redux-thunk,改造store.dispatch
,使得後者能夠接受函數做爲參數。
所以,異步操做的一種解決方案就是,寫出一個返回函數的 Action Creator,而後使用redux-thunk中間件改造store.dispatch。
改造後的dispatch處理addGunAsync函數生成的action(一個函數):
// action creator
export function buyHouse() {
return {type: BUY_HOUSE}
}
function buyHouseAsync() {
return dispatch => {
setTimeout(() => {
dispatch(buyHouse())
}, 2000)
}
}
dispatch(buyHouseAsync()) = middleware(midApi)(store.dispatch)(buyHouseAsync())
複製代碼
所以redux-thunk對應代碼以下:
const thunk = ({dispatch, getState}) => next => action => {
// next爲原生的dispatch
// 若是action是函數,執行一下,參數是dispatch和getState
if (typeof action === 'function') {
return action(dispatch, getState)
}
// 默認直接用原生dispatch發出action,什麼都不作
return next(action)
}
複製代碼
即判斷action若是是一個函數,則執行這個函數。不然直接用原生dispatch發出action,什麼都不作
這樣咱們就能夠經過redux-thunk中間件,實現了加強版的dispatch能夠接收函數做爲參數,而咱們在函數裏面進行異步操做,異步操做完成後用原生dispatch發出action,從而實現了redux的異步操做全局狀態的功能。
另外最近正在寫一個編譯 Vue 代碼到 React 代碼的轉換器,歡迎你們查閱。