Redux源碼(三) —— applyMiddleware.js

Source Time

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
    }
  }
}
複製代碼

爲了便於理解,我將源碼中的箭頭函數全都改成具名函數(以fn加上數字標記),以便於對照分析:javascript

function applyMiddleware(...middlewares) {
  return function fn1(createStore) {
    return function fn2(...args) {
      const store = createStore(...args);
      let dispatch = function fun3() {
        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: function fn4(...args) {
          dispatch(...args);
        },
      };
      const chain = middlewares.map(function fn5(middleware) {
        return middleware(middlewareAPI);
      });
      dispatch = compose(...chain)(store.dispatch);
      return {
        ...store,
        dispatch,
      };
    };
  };
}
複製代碼

Analysis

在詳細分析applyMiddleware源碼以前,咱們須要知道的是,中間件middleware的功能是對redux提供的dispatch進行一些擴展,或者說是加強,好比說最多見的logger中間件,它的任務就是在dispatch的過程當中順帶着實現一下打印日誌的任務,那麼這樣看來,其實applyMiddleware就像名字同樣,其做用就是告訴redux怎麼處理這些中間件,或者更具體的說,怎麼把中間件運用在redux提供的dispatch上。java

好了,下面開始逐條分析源碼(這裏便於定位,實際上是對照改寫後的函數)redux

1. 建立applyMiddle函數

applyMiddleware函數接受一箇中間件的散列,並用rest參數收集到middlewares這個數組中,最終會返回一個函數fn1。api

2. fn1的做用描述

函數fn1只有兩個做用,第一是接受惟一的參數createStore,沒錯就是redux提供的createStore方法;第二返回一個函數fn2。若是看過前一篇文章compose的應該能夠看出,這裏運用了柯里化的思想。數組

3. fn2的做用描述

函數fn2接受一個rest參數(結合createStore方法能夠知道其實就是reducerpreloadState),同時在內部詳細處理了如何將多箇中間件和reduxdispatch有機結合,如下幾點逐行描述。app

3.1 建立store

利用最基本的createStore建立一個store備用;函數

const store = createStore(...args);
複製代碼

3.2 建立dispatch

本地建立一個dispatch函數fn3,目前這個dispatch很是簡單,就是拋出一個錯誤。post

let dispatch = function fun3() {
  throw new Error(
    `Dispatching while constructing your middleware is not allowed. ` +
      `Other middleware would not be applied to this dispatch.`
  );
};
複製代碼

3.3 建立中間件須要的入參

建立middlewareAPI,能夠看到這個對象中只包含了兩個屬性:getStatedispatch。getState返回的是上面建立的store的getState方法,以便在中間件中可以獲取到state;而dispatch則是一個新的函數fn4,它接受rest參數args,並在內部調用5中提到的本地建立的dispatch(即fn3),注意這裏須要傳入參數,哪怕定義dispatch的時候並無定義。ui

const middlewareAPI = {
  getState: store.getState,
  dispatch: function fn4(...args) {
    dispatch(...args);
  },
};
複製代碼

3.4 重頭戲——組合中間件爲一個鏈

因爲這部分嵌套比較多,因此決定引用一個logger中間件來參照:this

// logger中間件
// 第一層
const logger = store => {
  // 第二層
  return next => {
    // 第三層
    return action => {
      console.group(action.type);
      console.info('dispatching', action);
      let result = next(action);
      console.log('next state', store.getState());
      console.groupEnd(action.type);
      return result;
    };
  };
}

// source code
const chain = middlewares.map(function fn5(middleware) {
  return middleware(middlewareAPI);
});
dispatch = compose(...chain)(store.dispatch);
複製代碼

對applyMiddlewares中接受到的rest參數middlewares使用一個map遍歷,處理函數fn5的工做是將middlewareAPI注入到中間件中,以logger爲例,這裏的middlewareAPI就是logger的入參store,同時返回第二層的函數(以next爲入參),因此咱們知道,chain其實就是把全部的中間件都先行注入middlewareAPI後的以next爲入參的函數組成的數組,或者能夠說,chain中包含了一系列函數,這些函數都被統一注入了middlewareAPI,而且接受next做爲入參。

順帶提一下,由middlewareAPI也能夠看出,在編寫中間件的時候,能拿到的store的api其實就只有兩個——getState和dispatch,而且這裏的dispatch仍是看上去沒什麼用的本地建立的西貝貨dispatch,只能拋出錯誤。

接着就用到了上一篇提到的compose函數了,這裏是接受chain數組展開的散列做爲參數,返回一個由多箇中間件嵌套的函數,這個函數接受store.dispatch做爲入參,參考logger就是第二層中的next參數,這樣咱們就獲得了一個處理過的、包含了中間件加強功能的dispatch函數(此時已經將那個只會throw Error的dispatch替換掉了)。

若是看過compose就能夠知道,入參的順序決定執行的順序,因此咱們通常將logger中間件儘量放在applyMiddleware的後面就能夠理解了,爲了第一步就執行logger。

4. 收尾

最終返回一個對象做爲用戶建立的store,和普通的由createStore建立的store不一樣的地方在於,這裏提供的dispatch是通過了處理的,從這一點上也能很明確看到以前提過的,中間件的做用就是對普通的dispatch進行了加強。

return {
  ...store, // 這個store由createStore在本函數中建立
  dispatch,
};
複製代碼

All

相關文章
相關標籤/搜索