淺談redux 中間件的原理


在使用redux管理異步數據流的時候,我們會使用中間件,以redux-thunk中間件為例,我們做一下分析:

首先是構建store,我們需要以下代碼進行揉入中間件的類似creatStore函數的構造:

const loggerMiddleware = createLogger();

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware,
  loggerMiddleware
)(createStore);

export default function configureStore(initialState) {
    return createStoreWithMiddleware(rootReducer, initialState);
}

在這段代碼中,我們用到了 

applyMiddleware 函數去將中間件揉入構造store的工廠函數中,
applyMiddleware函數的源碼如下所示:
import compose from './compose'
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var dispatch = store.dispatch
    var chain = []
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}
先看函數的return,我們通過applyMiddleware構建得到的createStoreWithMiddleware函數其實是這樣一個函數

function (reducer, initialState, enhancer){
      .......
      return {
          ...store,
          dispatch
     }

}

而store就是它return出來的這個對象

 

store的建立流程大致就是這樣,但此時store的dispatch方法已經不是原來的dispatch,注意下面的代碼:

chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)

這才是 applyMiddleware 方法的核心,它將每個middleware進行處理,並存入到chain 的數組中,然后調用compose方法對chain數組進行處理,處理出來的返回值就是store的dispatch,也就是我們在業務代碼中用到的dispatch

接下來看一下compose方法做了什么:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  }
}

它通過調用數組的reduceRight方法對各個中間件進行整合

reduceRight方法的 第一參數是 callback(preValue,curValue) ,第二個參數是要傳遞給callback作為第一個參數preValue來使用的

這時我們回到applyMiddleware方法中的這段代碼:

var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))

dispatch = compose(...chain)(store.dispatch)

由此我們可以知道傳給last(...args)的正是 store.dispatch

我們根據官方文檔可以知道中間件的通用構造如下:(箭頭函數這里略過)

function middleware({dispatch, getState}) {
    return function (next) {
        return function (action) {
            return next(action);
        }
    }
}

chain數組里面的數據結構是這樣的 :[ function(next){return function(action){...}},function(next){return function(action){...}} ...]

也就是 middleware函數的里面一層 的這個函數

function (next) {
        return function (action) {
            return next(action);
        }
    }

然后再經過compose的進一步reduceRight提煉:

return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))

compose就是 function(action){return next(action)}
在經過 f(composed) 仍是   function(action){return next(action);} 只不過這里的next是上一個中間件的返回值,追溯源頭,next其實就是 store.dispatch一路經過f(composed)這種方式將中間件的方法揉和進來的變種dispatch

所以經過揉入中間件的createStore工廠函數返回的store對象的dispatch方法,其實就是function(action){return next(action);}

然后結合實際中我們使用redux-thunk進行異步數據操作,thunk源碼如下:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;


//箭頭函數轉變正常就是這樣

function createThunkMiddleware(extraArgument) {
  return function({ dispatch, getState }){
       return  function(next){
             return function(action){
               if (typeof action === 'function') {
                 return action(dispatch, getState, extraArgument);
               }
               return next(action);
            }
     }
  };
}

通過上面分析,我們的store.dispatch就是這個東西

           function(action){
               if (typeof action === 'function') {
                 return action(dispatch, getState, extraArgument);
               }
               return next(action);
            }

我們在調用異步獲取數據的時候 action是這樣寫的:

export function fetchUri(key){
    return function(dispatch){
        dispatch(request("show"));
        return $.ajax({
            url:BOOKLIST_REQ.uri,
            dataType:"jsonp",
            data:{q:key,count:BOOKLIST_REQ.count}
        }).done(res=>{
            dispatch(receive(res));
            dispatch(request("hidden"));
        }).fail(res=>console.error(res));
    };
}

激發調取異步數據方法是  store.dispatch(fetchUrl("xxx"));

這樣后面的就不用細說了,怎么執行下來就一目了然了吧

這就是整個中間件大致的工作過程,如果有什么說的不對的地方,你特么來打我呀!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM