Redux中間件理解

看完此篇文章你能夠了解到原文連接react

  1. 中間件如何使用?
  2. 如何自定義中間件?
  3. redux-thunk異步執行action其實思想很簡單
  4. applyMiddleware是如何執行中間件的?

redux 的核心思想爲:將須要修改的 state 都存入到 store 裏,發起一個 action 用來描述發生了什麼,用 reducers 描述 action 如何改變 state tree 。建立 store 的時候須要傳入 reducer,真正能改變 store 中數據的是 store.dispatch API。git

1、 概念

中間件是 dispatch 一個 action 到觸發 reducer 之間作的一個額外操做,一般使用中間件 Middleware 來進行日誌記錄、建立崩潰報告、調用異步接口、路由、或者改變dispatch;github

2、 中間件的使用

import { createStore, applyMiddleware, combineReducers } from "redux";
import rootReducer from "./reducers/index";
import thunk from "redux-thunk";
const store = createStore(
  combineReducers({ ...rootReducer }),
  applyMiddleware([thunk])
);
複製代碼

此處使用了異步 action 中間件 thunk,沒錯就是傳入給 applyMiddleware 便可完成 dispatch 的加強。那麼有兩個問題?redux

  1. 當有多箇中間件時,每個 middleware 是如何操做前一箇中間件包裝過的 dispatch?
  2. 如何編寫本身的中間件?

3、 applyMiddleware 的理解

applyMiddleware 便可回答第 2 個問題,applyMiddleware 函數接受一箇中間件數組,並依次執行中間件,將上一個 middleware 包裝過的 store.dispatch 傳遞給下一個中間件數組

一、 一個簡單的 applyMiddleware

//一個簡單的 applyMiddleware 實現(非官方的 API,後面會介紹)
function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse(); //爲什麼要反序?

  /** 因爲是依次執行中間件,那麼當前中間件執行完成確定得執行下一個中間件,作到鏈式調用; 之因此將列表反序的目的是爲了在遍歷的時候,讓上一個中間件知道下一個中間件的dispatch是什麼;(可能這裏有點繞,下面講述Redux API的時候會介紹) **/
  let dispatch = store.dispatch;
  middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)));

  return Object.assign({}, store, { dispatch });
}

//提早透露:一個簡單的中間件,每個中間件中須要有當前的store和下一個dispatch。

const logger = store => next => action => {
  console.log("dispatching", action);
  let result = next(action); //next爲下一個dispatch;
  console.log("next state", store.getState());
  return result;
};
複製代碼

理解:app

  1. 中間件的執行是順序執行的,爲了可以鏈式執行中間件,須要在每個中間件中知道下一個 dispatch,這樣就能夠跳轉到下一個中間件;異步

  2. 每一箇中間件的dispatch生成實際上是反序的,由於 A 在調用時須要知道 B 的 dispatch,B 在執行時須要知道 C 的 dispatch,那麼須要先知道 C 的 dispatch。(下面 Redux API 源碼會驗證這點)async

  3. 在每個中間件中,都是可使用 next 函數(也就是下一個的 dispatch 函數);函數

二、 Redux 的 applyMiddleware 源碼理解

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args); //取到當前的store
    let dispatch = () => {
      throw new Error(
        "Dispatching while constructing your middleware is not allowed. " +
          "Other middleware would not be applied to this dispatch."
      );
    };

    const middlewareAPI = {
      //每一個 middleware 接受 Store 的 dispatch 和 getState 函數做爲命名參數
      getState: store.getState, //返回應用當前的 state 樹。
      dispatch: (...args) => dispatch(...args)
    };
    // 依次調用每個中間件
    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    //如今 此處的chain應該是一個函數數組[],一個相似於
    /** [ function(next){ return function(action){ } }, function(next){ return function(action){ } } ] **/

    //compose(...functions)從右到左來組合多個函數
    //做用:compose(funcA, funcB, funcC) 形象爲 compose(funcA(funcB(funcC())));
    // 其效果相似於上一部分講述的在循環中獲得上一個dispatch
    dispatch = compose(...chain)(store.dispatch);

    return {
      ...store,
      dispatch
    };
  };
}
複製代碼

理解:fetch

  1. 上面簡單例子講到,dispatch的生成實際上是反序的能夠從 compose 中看出端倪:compose(funcA, funcB, funcC) 形象爲 compose(funcA(funcB(funcC())));

  2. 看 conmpose 源碼其實你會發現,最後 compose(...chain)的結果應該爲:

function(){
    funcA(funcB(funcC()))
}

複製代碼

因此在執行compose(...chain)(store.dispatch)的時候,內部其實先調用了 funcC 來生成 C 的 dispatch。

  1. 最後一箇中間件中不該該調用 next 函數,由於沒有下一個中間件了,同理要是中間某個中間件沒有調用 next(action),那麼後面全部的中間件將不會被調用。(這就是官方文章中寫的:logger 中間件要放在最後一個的緣由)

三、 驗證中間件是順序執行,可是 dispatch 確實反序生成的

此處可能有點超前,若是您不知道如何編寫中間件請先閱讀下一節,再回到這裏來看

//第一個中間件
function createMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    console.log("第一個的下一個的dispatch", next);

    console.log("第一個action", action);
    const result = next(action);
    console.log("第一個state", getState());
    return result;
  };
}

const firstMid = createMiddleware();
export default firstMid;
複製代碼
//第二個中間件
function createMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    console.log("第二個的下一個的dispatch", next);

    console.log("第二個action", action);
    const result = next(action);
    console.log("第二個state", getState());
    return result;
  };
}

const secondMid = createMiddleware();
export default secondMid;
複製代碼
//中間件使用
const middlewares = [firstMid, secondMid]; //注意中間件的順序

const store = createStore(
  combineReducers({ ...rootReducer }),
  composeWithDevTools(applyMiddleware(...middlewares))
);

//實際打印的結果
/** 第一個的下一個的dispatch ƒ (action) { console.log('第二個的下一個的dispatch', next); console.log('第二個action', action); var result = next(action); console.log('第二個state', getState()); return resu… 第一個action {type: "GLOBAL_DATA", globalData: {…}} 第二個的下一個的dispatch ƒ dispatch(action) { if (!isPlainObject(action)) { throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.'); } if (typeof action.type === 'unde… 第二個action {type: "GLOBAL_DATA", globalData: {…}} 第二個state {routing: {…}, global: {…}, home: {…}} 第一個state {routing: {…}, global: {…}, home: {…}} **/
複製代碼

4、 如何編寫本身的中間件?

格式爲:

function yourMiddleware() {
  return ({ getState, dispatch }) => next => action => {};
}
複製代碼

...middlewares (arguments): 遵循 Redux middleware API 的函數。每一個 middleware 接受 Store 的 dispatch 和 getState 函數做爲命名參數,並返回一個函數。該函數會被傳入 被稱爲 next 的下一個 middleware 的 dispatch 方法,並返回一個接收 action 的新函數;

這個函數能夠直接調用 next(action),或者在其餘須要的時刻調用,甚至根本不去調用它。調用鏈中最後一個 middleware 會接受真實的 store 的 dispatch 方法做爲 next 參數,並藉此結束調用鏈。因此,middleware 的函數簽名是 ({ getState, dispatch }) => next => action。

一個記錄日誌的中間件:

function createLoggerkMiddleware() {
  return ({ dispatch, getState }) => next => action => {
    console.log("will dispatch", action);
    // 調用 middleware 鏈中下一個 middleware 的 dispatch。
    let returnValue = next(action);

    console.log("state after dispatch", getState());

    // 通常會是 action 自己,除非
    // 後面的 middleware 修改了它。
    return returnValue;
  };
}

const logger = createLoggerkMiddleware();
export default logger;
複製代碼

理解:

  1. Redux middleware 就像一個鏈表。每一個 middleware 方法既能調用 next(action) 傳遞 action 到下一個 middleware,也能夠調用 dispatch(action) 從新開始處理,或者什麼都不作而僅僅終止 action 的處理進程。

5、 異步 Action redux-thunk 的理解

一、redux-thunk的使用例子

const middlewares = [thunk, middleware];
const store = createStore(

  combineReducers({ ...rootReducer }),
  composeWithDevTools(applyMiddleware(...middlewares))
);

//action中
//這是一個同步action
const receiveInfo = response => ({
  type: 'RECEIVE_HOME',
  homeInfo: response
});

//使用redux-thunk異步執行action
export const getInfo = () => async (dispatch, getState) => {
  try {
    const response = await new Promise((resolve, reject) => {
      /* 模擬異步操做成功,這樣能夠經過fetch調接口獲取數據 */
      setTimeout(() => {
        resolve({ title: 'React App' });
      }, 1000);
    });
    await dispatch(receiveInfo (response));//使用dispatch觸發同步action
    return response;
  } catch (error) {
    console.log('error: ', error);
    return error;
  }
};

//在react中
let {getInfo}=this.props;
getInfo().then({

})

複製代碼

二、redux-thunk源碼解析

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
      //能夠接受一個返回函數的action creator。若是這個action creator 返回的是一個函數,就將dispatch的決策權交給此函數,若是不是,就按照原來的next(action)執行。
    if (typeof action === "function") {
      return action(dispatch, getState, extraArgument);//這就是上面例子的函數爲啥接受dispatch和getState兩個參數的緣由
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
複製代碼
相關文章
相關標籤/搜索