Redux中間件的實踐

最近項目前端開發框架採用React+Redux進行實現,可是,如何異步訪問服務器端,以及想要在開發過程當中進行狀態樹日誌的輸出,因此怎麼才能解決這兩個問題? 採用Redux中間件前端

爲何要使用中間件

在利用Redux進行狀態管理時,用戶在UI層面觸發行爲,一個action對象經過store.dispatch派發到Reducer進行觸發,接下來Reducer會根據type來更新對應的Store上的狀態樹,更改後的state會觸發對應組件的從新渲染。以下圖所示。在這個流程中,action對象是一個同步的對象,是一個包含type字段的簡單對象,可是在訪問服務器時,因爲瀏覽器是單線程的,不會一遍渲染組件一遍等待服務器返回的結果,所以咱們須要設計一種異步訪問服務器的方法來實現與服務器端的正常通訊。
圖片描述node

中間件介紹

在Redux架構下,一個action對象在經過store.dispatch派發,在調用reducer函數前,會先通過一箇中間件環節,以下圖所示。
圖片描述react

從上圖能夠看出,在一個Redux架構中能夠用多箇中間件,這些中間件一塊兒組織處理請求的「管道」。一箇中間件是一個獨立的函數,能夠組合使用,中間件有一個統一的接口,正由於一箇中間件只能完成一個特定的功能,因此把多箇中間件組合在一塊兒才能知足比較豐富的應用需求。固然在使用時,也須要按照順序依次處理傳入的action,只有排在前面的中間件完成任務以後,後面的中間件纔能有機會繼續處理action。
圖片描述npm

中間件的特色

  • 中間件是獨立的函數
  • 中間件能夠組合使用
  • 中間件有一個統一的接口

中間件接口

每一箇中間件必須定義爲一個函數,返回一個接受next參數的函數,而這個接受next參數的函數又返回一個接受action參數的函數。next參數自己也是一個函數,中間件調用這個next函數通知Redux本身的處理工做已經結束。
一個什麼都不作的中間件代碼以下:redux

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

包含的功能有:api

  • 調用dispatch派發出一個新的action對象
  • 調用getState得到當前Redux Store上的狀態
  • 調用next告訴Redux當前中間件工做完畢,讓Redux調用下一個中間件
  • 訪問action對象action上的全部數據

在一個Redux應用若是想要使用中間件,必須經過applyMiddleware來生成。Redux的源碼文件很是簡單,由五個文件一塊兒組成,分別是createStore.js,applyMiddlware.js,compose.js,bindActionCreator.js,combineReducers.js。與createStore是用來建立一個狀態樹,而且暴露出幾個方法,包括dispatch,subscribe,getState,replaceReducer和$$observable,給createStore傳入的參數有reducer,preloadedState和enhancer,其中enhancer就是一個store加強器,是一個函數,只能用applyMiddleware生成。applyMiddleware函數是根據外部函數(中間件函數)包裝原來的dispatch函數,而後將新的dispatch函數暴露出去。數組

//根據外部函數(中間件函數)包裝原來的dispatch函數,而後將新的dispatch函數暴露了出去
export default function applyMiddleware(...middlewares) {
  //return一個函數,它能夠接受createStore方法做爲參數,給返回的store的dispatch方法再進行一次包裝
  return createStore => (...args) => {//agrs包含reducer, preloadedState, enhancer
    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)
    }
    //傳入middlewareAPI參數並執行每個外部函數,返回結果匯聚成數組
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    //這裏用到了compose方法
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

中間件與加強器的區別

中間件和加強器都是對Redux Store的加強,可是中間件僅僅是對Redux Store的dispatch方法進行了加強,也就是從dispatch函數調用到action對象被reducer處理這個過程當中的操做,加強器是對Redux Store進行更深層次的加強定製,須要使用Store Enhancer,經過閱讀加強器接口,一個加強器其實利用隨給的參數創造出一個store對象,而後定製對象,最後把Store對象返回。總的對好比下:瀏覽器

  • 中間件: 能夠用來加強redux store的dispatch函數,也就是從dispatch函數調用到action對象被reducer處理這個過程當中的操做
  • 加強器: 對redux store進行更深層次的加強定製,能夠加強redux store的各個方面。

異步訪問服務器

異步action對象

在沒有引入中間件時,社會治理子系統在開發時,全部的action都是同步的,一個同步的action對象是一個包含type字段的簡單對象,可是咱們須要實現一個異步action對象,是一個函數,在action觸發以後,在reducer接收到執行命令以前能夠進行一個異步操做。
咱們引入redux-thunk來實現異步訪問服務器方法,一個訪問服務器的action,至少要涉及三個action類型:服務器

  • 表示異步操做已經開始的action類型;
  • 表示異步操做成功的action類型;
  • 表示異步操做失敗的action類型;

Redux-thunk源代碼解析

Redux-thunk中間件是Redux中異步操做的解決方法之一,在action對象被reducer函數處理以前,是插入異步功能的時機,代碼很是簡單:架構

function create ThunkMiddleware(extraArgument){
    return ({dispatch, getState}) => next => action => {
        if(typeof action === ‘function’){
         return action(dispatch, getState, extraArgument);
     }
     return next(action)
    }
}
const thunk = createThunkMiddleware();
export default thunk;

createThunkMiddleware函數返回了一個函數,是實際處理每一個action對象的函數,首先檢查參數action的類型,若是是函數類型的話,就執行這個action函數,把dispatch和getState
做爲參數傳遞出去,不然就調用next讓下一個中間件繼續處理action。

Redux-thunk的使用:

首先,安裝redux-thunk,在已經安裝了node.js的命令窗口中運行 「npm install redux-thunk --save-dev」,在store.js中引入redux-thunk,而且確保redux的applyMiddleware函數也引入。具體實現代碼以下。

import {createStore, combineReducers, applyMiddleware} from ‘redux’;
import {otherState, dataState} from ‘reducers’;
import thunkMiddleware from ‘redux-thunk’;
var reducers = combineReducers({
    otherState,
    dataState
});
var store = createStore(reducers, applyMiddleware(thunkMiddleware));
export default store;

在成功引入了redux-thunk後,咱們也要設計異步操做的action對象,例如,在管理模塊中,成功保存信息後要從新獲取信息,代碼以下:

function saveInfo(params){
    let url = 「/api/device」;
    return function(dispatch, getState){
        dispatch(saveInfoRequest());
        return Http.get(url, {
            params: params
        }).then(res=>{
            if(res && res.type === 0){
                dispatch(saveInfoSuccess ());
                let dataState = getState().dataState;
                let newParams = {
                    start: dataState.start,
                    limit: dataState.limit,
                    keyword: dataState.keyword
                };
               dispatch(getInfo(newParams))
            }
        }).catch(error=>{
              dispatch(saveInfoFailure (error));
        });
    }
}

從這個saveDeviceInfo返回的函數中,不只能夠dispatch一個同步的action對象,還可派發另外一個異步action對象,來知足一些有着前後關係的業務邏輯,代碼可讀性要比用Promise實現起來代碼更加清晰。

Redux-logger使用

在開發階段,咱們須要對redux數據流中每一個流程進行監控,須要log輸出,redux-logger是官方推薦的一款日誌中間件,使用起來很是方便。固然,要使redux-logger生效,須要保證在系統中使用redux進行狀態管理,不然沒有任何日誌輸出。
Redux-logger的使用方法能夠分爲兩種,基本使用方法以下:

import { applyMiddleware, createStore} from ‘redux’;
import logger from ‘reudx-logger’
const store = createStore(
    Reducer,
    applyMiddleware(logger)
)

也能夠本身寫一個日誌輸出中間件,代碼以下:

var logger = store => next => action => {
    console.log('[action]', action)
    console.log(`[action] type:${action.type} payload:${JSON.stringify(action.payload)}`)
    next(action)
    console.log('[store]', store.getState())
    console.log(`[store] ${JSON.stringify(store.getState())}`)
}

總結

Redux中間件能夠加強Store.dispatch方法,多箇中間件能夠組成「管道」,按照順序去處理action對象,在依次處理事後,纔會有機會被reducer處理。中間件的應用場景不少,除了能夠支持異步訪問服務器,還有許多很好的中間件插件,例如react-addons-perf進行調試,和redux-logger來記錄狀態,也能夠根據業務需求來本身編寫中間件,應用很是靈活,在其餘react項目中能夠多加實踐。

相關文章
相關標籤/搜索