提到中間件,你可能會想到Express
和Koa
等服務端框架,沒想到也不要緊,這句話是我裝逼用的。javascript
那麼redux中的中間件到底幹嗎用的?java
有這樣一個問題?咱們以前用的Redux
都是在Action
發出以後當即執行Reducer
,計算出state
,這是同步操做。若是想異步操做呢?即過一段時間再執行Reducer
怎麼辦?這裏就須要用到中間件middleware
。redux
先放一張圖看看:數組
redux
是有流程的,那麼,咱們該把這個異步操做放在哪一個環節比較合適呢?promise
Reducer
?純函數只承擔計算State
功能,不適合其它功能。View
?與State
一一對應,能夠看作是State
的視覺層,也不適合承擔其它功能。Action
?它是一個對象,即存儲動做的載體,只能被操做。其實,也只有dispatch
能勝任此重任了。那麼怎麼在dispatch
中添加其它操做呢?bash
let next = store.dispatch;
store.dispatch = function(action){
console.log('老狀態 ',store.getState());
next(action);
console.log('新狀態 ',store.getState());
}
複製代碼
示例中能夠看出,咱們對store.dispatch
從新進行了定義,在發送action
的先後,作了打印。app
這是中間件的大體雛形,真實的中間件要比這麼複雜多了框架
咱們在這裏先看看中間件是怎麼使用,下面咱們一步步剖析每一個細節。dom
import {applyMiddleware,createStore} from 'redux';
import reduxLogger form 'redux-logger';
const store = createStore(reducer,inital_state,applyMiddleware(thunk, promise,reduxLogger));
複製代碼
代碼中有兩點須要注意:異步
createStore
方法能夠整個應用的初始狀態做爲參數 內部是這麼處理的let state = inital_state;
複製代碼
Middleware可讓你包裝store
的dispatch
方法來達到你想要的目的。同時,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
能夠拿到getState
和dispatch
這兩個方法。
...middleware
:遵循Redux middleware API
的函數。每一個middleware
接受Store
的dispatch
和getState
函數做爲命名參數,並返回一個函數。該函數會被傳入成爲next
的下一個middleWare 的dispatch方法,並返回一個接收action的新函數,這個函數能夠直接調用next(action),或者在其餘須要的時刻調用,甚至根本不去調用它。
因此,接下來,咱們就能看到middleware的函數簽名是({ getState, dispatch }) => next => action
其實,它的本質就是包裝sotre中的dispatch
。
上面代碼中,還用到了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
的參數有順序,這裏給出了答案。
使用 Redux 的一個益處就是它讓 state 的變化過程變的可預知和透明。每當一個 action 發起完成後,新的 state 就會被計算並保存下來。State 不能被自身修改,只能由特定的 action 引發變化。
試想一下,當咱們的應用中每個 action 被髮起以及每次新的 state 被計算完成時都將它們記錄下來,豈不是很好?當程序出現問題時,咱們能夠經過查閱日誌找出是哪一個 action 致使了 state 不正確。
圖片的效果是否是很期待啊!!!
咱們先來手動實現一版。
// 記錄全部被髮起的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());
}
複製代碼
redux-thunk
是redux
官方文檔中用到的異步組件,實質就是一個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));
複製代碼
redux-promise
也是延遲執行的表達式,它是解決異步的另一種方案。
redux-thunk
和核心思想是把action
變成thunk
,而redux-promise
的核心思想是讓action
返回一個promise對象。
這個中間件使得store.dispatch
方法能夠接收Promise對象做爲參數。這時 ,action 有兩種寫法:
寫法1、返回值是一個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
對象,那麼不管resolve
和reject
,dispatch 方法都會發出Action
。