redux中介軟體

romin發表於2019-03-01

前言

提到中介軟體,你可能會想到ExpressKoa等服務端框架,沒想到也沒關係,這句話是我裝逼用的。

那麼redux中的中介軟體到底幹嘛用的?

有這樣一個問題?我們之前用的Redux都是在Action發出之後立即執行Reducer,計算出state,這是同步操作。如果想非同步操作呢?即過一段時間再執行Reducer怎麼辦?這裡就需要用到中介軟體middleware

先放一張圖看看:

redux中介軟體

一、中介軟體的概念

redux是有流程的,那麼,我們該把這個非同步操作放在哪個環節比較合適呢?

  • Reducer?純函式只承擔計算State功能,不適合其它功能。
  • View?與State一一對應,可以看做是State的視覺層,也不適合承擔其它功能。
  • Action?它是一個物件,即儲存動作的載體,只能被操作。

其實,也只有dispatch能勝任此重任了。那麼怎麼在dispatch中新增其它操作呢?

let next = store.dispatch;
store.dispatch = function(action){
   console.log('老狀態 ',store.getState());
   next(action);
   console.log('新狀態 ',store.getState());
}
複製程式碼

示例中可以看出,我們對store.dispatch重新進行了定義,在傳送action的前後,做了列印。

這是中介軟體的大致雛形,真實的中介軟體要比這麼複雜多了

二、中介軟體的用法

我們在這裡先看看中介軟體是怎麼使用,下面我們一步步剖析每個細節。

import {applyMiddleware,createStore} from 'redux';
import reduxLogger form 'redux-logger';

const store = createStore(reducer,inital_state,applyMiddleware(thunk, promise,reduxLogger));

複製程式碼

程式碼中有兩點需要注意:

  • 1、createStore方法可以整個應用的初始狀態作為引數 內部是這麼處理的
let state = inital_state;
複製程式碼
  • 2、中介軟體的引數次序有講究。下面我會把這個問題講明白。

三、applyMiddleware

Middleware可以讓你包裝storedispatch方法來達到你想要的目的。同時,middleWare還擁有“可組合”這一關鍵特性。多個middleWare可以被組合到一起使用,形成middleWare鏈,依次執行。其中每個middleware不需要關心鏈前後的的middleWare的任何資訊。

function applyMiddleware(...middlewares){
    return function(createStore){
        return function(reducer){
            //引入store
            let store = createStore(reducer);
            let dispatch = store.dispatch;
            let middlewareAPI = {
                getState:store.getState,
                // 對dispatch進行包裝
                dispatch:action=>dispatch(action)
            }
            //每個中介軟體都是這種模型  ({ getState, dispatch }) => next => action
            chain = middlewares.map(middleware=>middleware(middleAPI));
            dispatch = compose(...chain)(store.dispatch);
            // dispatch被改裝後,返回store
            return{...store,dispatch};
        }
    }
}
複製程式碼

上面程式碼中,所有中介軟體都被放進了一個陣列chain,然後巢狀執行,最後執行store.dispatch。中介軟體內部middlewaAPI可以拿到getStatedispatch這兩個方法。

...middleware:遵循Redux middleware API的函式。每個middleware接受StoredispatchgetState函式作為命名引數,並返回一個函式。該函式會被傳入成為next的下一個middleWare 的dispatch方法,並返回一個接收action的新函式,這個函式可以直接呼叫next(action),或者在其他需要的時刻呼叫,甚至根本不去呼叫它。

所以,接下來,我們就能看到middleware的函式簽名是({ getState, dispatch }) => next => action

其實,它的本質就是包裝sotre中的dispatch

上面程式碼中,還用到了compose方法,我們來看看compose是怎麼是實現的?

compose

先看下面一個栗子:

function add1(str){
   return str+1;
}
function add2(str){
    return str+2;
 }
 function add3(str){
    return str+3;
 }
 let result = add3(add2(add1('好吃')));// 好吃123;
 
複製程式碼

這中寫法呼叫起來,一層套一層,是不是看著很不爽,我們簡化一下:

function compose(...fns){
    if(fns.length==1)
     return fns[0];
   return function(...args){
    let last = fns.pop();
    return fns.reduceRight((prev,next)=>{
         return  next(prev);  
    },last(...args));
   }
 }
 let add = compose(add3,add2,add1);//
 let result = add('好吃');// 好吃123
 // 上面的程式碼其實就是redux3.6.0版本中compose的實現方式
複製程式碼

看看這個程式碼是不是用起來,很乾練一些。其實還可以簡化

 function compose(...fns){
  if(fns.length==1)
     return fns[0];
   return fns.reduce((a,b)=>(...args)=>a(b(...args)));//add3(add2(add1('好吃')))
 }
 let add = compose(add3,add2,add1);//
 let result = add('好吃');// 好吃123
 // 這是redux3.6.0版本之後的compose實現方式,一直沿用至今。
複製程式碼

至於為什麼applyMiddleWare的引數有順序,這裡給出了答案。

四、Applymiddleware的三個常用引數

4.1、日誌記錄

使用 Redux 的一個益處就是它讓 state 的變化過程變的可預知和透明。每當一個 action 發起完成後,新的 state 就會被計算並儲存下來。State 不能被自身修改,只能由特定的 action 引起變化。

試想一下,當我們的應用中每一個 action 被髮起以及每次新的 state 被計算完成時都將它們記錄下來,豈不是很好?當程式出現問題時,我們可以通過查閱日誌找出是哪個 action 導致了 state 不正確。

redux中介軟體

圖片的效果是不是很期待啊!!!

我們先來手動實現一版。

// 記錄所有被髮起的action和新的state
let next = store.dispatch;
store.dispatch = function(action){
   console.log('老狀態 ',store.getState());
   next(action);
   console.log('新狀態 ',store.getState());
}
複製程式碼

還是上面的示例,我們來做個修改

let logger = function({ getState, dispatch }){
   return function(next){// 這裡的next可以理解為store.dispath,本質上就是呼叫 middleware 鏈中下一個 middleware 的 dispatch。
      return function(action){
        console.log('老狀態1 ',getState());
        next(action);//派發動作
        console.log('新狀態1 ',getState());
    }
    }
}
// 高逼格寫法
let logger = ({ getState, dispatch }) => next => action => {
  console.log('老狀態1 ',getState());
  next(action)
  console.log('新狀態1 ',getState());
}
複製程式碼

4.2、redux-thunk 中介軟體

redux-thunkredux官方文件中用到的非同步元件,實質就是一個redux中介軟體,一個封裝表示式的函式,封裝的目的就是延遲執行表示式。

redux-thunk是一個通用的解決方案,其核心思想是讓action可以變成一個thunk,這樣的話,同步情況:dispatch(action),非同步情況:dispatch(thunk)

下面是redux-thunk的實現:

let thunk = ({dispatch,getState})=>next=>action=>{
    if(typeof action == 'function'){
        action(dispatch,getState);
    }else{
        next(action);//這裡可以理解為dispatch(action),本質上就是呼叫 middleware 鏈中下一個 middleware 的 dispatch。
    }
}
複製程式碼

使用redux-thunk

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

然後我們實現一個thunkActionCreator

    //過一秒加1
    export function thunkActionCreator(payload){
        return function(dispatch,getState){
            setTimeout(function(){
                dispatch({type:types.INCREMENT,payload:payload});
            },1000);
        }
    },
複製程式碼

最後,在元件中dispatch thunk

this.dispatch(thunkActionCreator(payload));
複製程式碼

4.3、redux-promise

redux-promise也是延遲執行的表示式,它是解決非同步的另外一種方案。

redux-thunk和核心思想是把action變成thunk,而redux-promise的核心思想是讓action返回一個promise物件。

這個中介軟體使得store.dispatch方法可以接收Promise物件作為引數。這時 ,action 有兩種寫法:

寫法一、返回值是一個Promise物件。

function promiseIncrement(payload){
 //  return {type:types.INCREMENT,payload:payload}  以前是這種寫法
    return new Promise(function(resolve,reject){
      setTimeout(function(){
        resolve({type:types.INCREMENT,payload:payload});
      },1000);
    });
 },
複製程式碼

寫法二,action 物件的payload屬性是一個Promise物件,這需要從

function payloadIncrement(){
    return {
        type:types.INCREMENT,
        payload: new Promise(function(resolve,reject){
            setTimeout(function(){
                if(Math.random()>.5){
                    resolve(100);
                }else{
                    reject(-100);
                }
            },1000)
        })
    }
}
複製程式碼

下面我們來看看 redux-promise是怎麼實現的,就會明白它內部是怎麼操作的.

let promise = ({dispatch,getState})=>next=>action=>{
    if(action.then && typeof action.then == 'function'){
        action.then(dispatch);
        // 這裡的dispatch就是一個函式,dispatch(action){state:reducer(state,action)};
    }else if(action.payload&& action.payload.then&& typeof action.payload.then == 'function'){
        action.payload.then(payload=>dispatch({...action,payload}),payload=>dispatch({...action,payload}));
    }else{
        next(action);
    }
}
複製程式碼

上面的程式碼可以看出,如果Action本身就是一個Promise,它resolve以後的值應該是一個Action物件,會被dispatch方法送出action.then(dispatch);如果Action物件的 payload屬性是一個Promise物件,那麼無論resolvereject,dispatch 方法都會發出Action

相關文章