喵了個咪!redux middleware居然如此簡單!

圓兒圈圈發表於2019-03-02

Redux解決了react中出現的多互動、多資料來源問題,但是如果有非同步操作,或者要對操作進行攔截或者執行後回撥就比較麻煩。於是我們需要Redux 中介軟體。

一、手動增強store.dispatch

我們知道 react-redux的 connect是一個高階元件,它將元件包裝之後拿到一個能夠在元件中直接獲取 context 的 state 的元件,並且用dispatch監聽每一個action:

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
    class Connect extends Component {
       ...
       let dispatchProps = mapDispatchToProps
                ? mapDispatchToProps(store.dispatch, this.props)
                : {} // 用來 dispatch 的時候獲取 store 的 dispatch
        ...    
        render() {
            return <WrappedComponent {...this.state.allProps}/>
        }
    }
    return Connect;
}

複製程式碼

如果要增強dispatch,我們可以對其進行重構,直接改寫 store 例項中的 dispatch:

let store = createStore(rootReducer);
let dispatch = store.dispatch//拿到dispatch
store.dispatch = function (action) {//對dispatch進行重構
    console.log(`舊狀態`,store.getState())//1.列印就狀態
    dispatch(action)//2.在執行之前的action
    console.log(`新狀態`,store.getState())//3.列印新狀態
}
複製程式碼

以上的程式碼增強了dispatch方法,使執行順序變成了action->log->reducer->log,執行結果如下:

喵了個咪!redux middleware居然如此簡單!

二、使用redux的applyMiddleware方法增強store.dispatch

redux 提供了類似後端 Express 的中介軟體概念,本質的目的是提供第三方外掛的模式,自定義攔截 action -> reducer 的過程。變為 action -> middlewares -> reducer 。這種機制可以讓我們改變資料流,實現如非同步 action ,action 過濾,日誌輸出,異常報告等功能。

官方說明如下:使用中介軟體擴充套件增強Redux store上的dispath 方法。因為中介軟體可能是非同步的,所以這應該是定義在組合鏈中儲存增強器。

redux applyMiddleware方法原始碼

export default function applyMiddleware(...middlewares) {//[middleware1,middleware2]
  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))//將middleware放入鏈式陣列
    dispatch = compose(...chain)(store.dispatch)//依次執行

    return {// 將增強過的dispatch返回
      ...store,
      dispatch
    }
  }
}
複製程式碼

applyMiddleware的使用方法,官方文件中給出兩種用法:

const store = createStore(reducer, preloadedState, applyMiddleware(...))

const store = createStore(reducer, applyMiddleware(...))
複製程式碼

第二個引數初始化state不是必傳的,原始碼中的createStore方法對引數進行了處理

三、middleware如何工作

redux createStore原始碼

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === `function` && typeof enhancer === `undefined`) {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== `undefined`) {
    if (typeof enhancer !== `function`) {
      throw new Error(`Expected the enhancer to be a function.`)
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  ......    
    
}
複製程式碼

在createStore方法中判斷引數並相應替換,最後createStore程式碼執行返回的是一個enhancer函式巢狀呼叫方法,也就是:

const store = applyMiddleware(...)(createStore)(reducer,preloadedState)
複製程式碼
喵了個咪!redux middleware居然如此簡單!

如圖所示:巢狀函式分別傳入的createStore和reducer,建立了store,並定義了dispatch方法,並組合成obj傳給了logger函式

logger中介軟體函式接受兩個引數 dispatch getState(獲取狀態 派發動作) 並返回一個新的引數 next,形成了一個dispatch增強函式。小白我對於這個一長串的return理解成如下:

let logger1 = function({dispatch,getState}) {
    store.dispatch = function(action){
        console.log(`舊狀態1`,getState())
        next(action)
        console.log(`新狀態1`,getState())
    }
}
複製程式碼

這已經跟文章開頭手動增強store.dispatch的函式十分相近了,主要區別在於next方法。

middleware 通過 next(action) 一層一層處理和傳遞 action,直到 redux 原生的 dispatch`,這時next為客戶端呼叫的dispatch方法,action為方法傳入的actionType:{type:xxx,payload:xxx}
咳,程式碼要優雅,小白我理解了就要按照官方的來,正確的middleWare一樣定義格式如下:

let logger = ({dispatch,getState}) => next => action =>{
    console.log(`舊狀態1`,getState())
    next(action)//dispatch(action)
    console.log(`新狀態1`,getState())
}
複製程式碼
喵了個咪!redux middleware居然如此簡單!

中介軟體的實現和洋蔥模型很像,先觸發logger第一層,再觸發dispatch事件,最後再從logger函式出來。

四、compose實現鏈式呼叫

實現多箇中介軟體先後呼叫的關鍵是compose函式

redux compose原始碼連結

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製程式碼

原始碼很精簡,理解有點複雜。其實是使用reduce不斷將最右先調動函式的結果返回給後呼叫的函式。

舉個例子,

function add1(str){
    return str+1;
}
function add2(str){
    return str+2;
}
function add3(str){
    return str+3;
}

let add = compose(add3,add2,add1);
let r = add("啊哈哈哈")//啊哈哈哈123
複製程式碼

在這段程式碼中,compose函式執行順序為add1->add2->add3,並將結果作為引數傳給下一個函式。

喵了個咪!redux middleware居然如此簡單!

在redux中當新 dispatch 執行時,[f1, f2, … , fx, …, fn],從右到左依次執行。

dispatch = f1(f2(f3(store.dispatch))))
複製程式碼

如圖所示,從右至左執行logger2,logger1。logger2返回的程式碼作為引數傳給logger1的next引數。按照圖上1->2(執行下一個middleware)->3->4(觸發redux 原生的 dispatch方法)->5->6 完成

喵了個咪!redux middleware居然如此簡單!

鏈式middleware流程圖如下

喵了個咪!redux middleware居然如此簡單!

五、非同步操作

很多時候,我們需要非同步操作。使用者觸發第一個dispatch事件的action,需要傳送第二個action。或者根據返回的根據傳送第二個處理請求。

解決非同步操作的方法:

(1)redux函式的引數是dispatch和getState,把返回的obj改成返回一個非同步函式。

(2)非同步操作結束之後,再發出一個 Action。

function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 1000);
  };
}
複製程式碼

這樣子能理想得解決非同步操作,而store.dispatch方法正常情況下,引數只能是物件,不能是函式。

這個時候可以引入redux-thunk

Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.

redux-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;
複製程式碼

redux-thunk 做的事情就是判斷 action 型別是否是函式,若是,則執行 action,若不是,則繼續傳遞 action 到下個 middleware。

運用方法:

let store = createStore(
    rootReducer,
    applyMiddleware(thunk,logger1,logger2)
)
複製程式碼

如圖所示,當store.dispatch執行到thunk中介軟體,發現返回的是一個function,則執行返回的函式,並返回,重新派發dispatch

喵了個咪!redux middleware居然如此簡單!

因此使用redux-thunk,改造store.dispatch。可以實現非同步方法

還有如 redux-promise redux-saga也可以解決非同步的問題

相關文章