文章首發於個人博客 https://github.com/mcuking/bl...相關代碼請查閱 https://github.com/mcuking/bl...react
上一講實現了react-redux,從而能夠更加優雅地在react中使用redux。git
可是,一個關鍵問題沒有解決:異步操做怎麼辦?Action 發出之後,Reducer 當即算出 State,這叫作同步;Action 發出之後,過一段時間再執行 Reducer,這就是異步。github
怎麼才能 Reducer 在異步操做結束後自動執行呢?這就要用到新的工具:中間件(middleware)。redux
爲了理解中間件,讓咱們站在框架做者的角度思考問題:若是要添加功能,你會在哪一個環節添加?app
只有發送 Action 的這個步驟,即store.dispatch()方法,能夠添加功能。舉例來講,要添加日誌功能,把 Action 和 State 打印出來,能夠對store.dispatch進行以下改造。框架
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 這兩步之間,添加了其餘功能。異步
在編寫中間件以前,咱們先看下在真正的redux裏面是如何使用中間件的,當使用單箇中間件時代碼以下,其中thunk就是一箇中間件:函數
const store = createStore(counter, applyMiddleware(thunk))
有上面的代碼能夠看出,咱們須要作三件事情工具
代碼以下,檢測是否有加強器,若存在則先用加強器對createStore進行擴展加強spa
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的異步操做全局狀態的功能。
相關文章以下: