mini-redux 實現原理講解 第三講

文章首發於個人博客 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

  • Reducer:純函數,只承擔計算State 的功能,不合適承擔其餘功能,也承擔不了,由於理論上,純函數不能進行讀寫操做。
  • View:與 State 一一對應,能夠看做 State 的視覺層,也不合適承擔其餘功能。
  • Action:存放數據的對象,即消息的載體,只能被別人操做,本身不能進行任何操做。

只有發送 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函數進行擴展,使其可以接收第二個參數——中間件
  • 第二步 定義applyMiddleware函數,使其可以將一箇中間件加入到redux中
  • 第三步 實現一箇中間件——redux-thunk

對createStore函數進行擴展

代碼以下,檢測是否有加強器,若存在則先用加強器對createStore進行擴展加強spa

export function createStore(reducer, enhancer) {
    // 若是存在加強器,則先用加強器對createStore進行擴展加強
    if (enhancer) {
        return enhancer(createStore)(reducer)
    }
    ...
}

定義applyMiddleware函數

根據上面的代碼,咱們能夠知道,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)

實現中間件redux-thunk

異步操做至少要送出兩個 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的異步操做全局狀態的功能。

相關文章以下:

相關文章
相關標籤/搜索