你為什么需要異步操作? https://stackoverflow.com/questions/34570758/why-do-we-need-middleware-for-async-flow-in-redux
在redux基礎篇的介紹中,我們介紹了redux的基本概念, 對於state的改變有了詳盡的了解,但是並沒有提到異步問題如何解決? 何為異步? Action 發出以后,Reducer 立即算出 State,這叫做同步;Action 發出以后,過一段時間再執行 Reducer,這就是異步。
在vue中對異步的實現是在actions下使用,如下所示:
getDefaultAddress ({commit, state}) { return new Promise(function (resolve, reject) { axios.get('/bbg/user/get_default_address', { params: { uid: localStorage.getItem("uid") } }).then(function (response) { if (response.data.code == 152) { console.log("獲取默認地址成功"); // 存儲默認地址 commit(UPDATE_DEFAULT_ADDRESS, response.data.data); } resolve(); }); }); },
這里是一個異步的操作,即首先執行getDefaultAddress, 然后在執行這個的過程中,我們需要等到返回結果之后再去改變state數據,而vue的方法就是在判斷成功的使用commit一個reducer,這樣,就可以完成異步的操作了。
那么react中是如何實現這種異步操作呢? 這時就需要使用工具: 中間件了。
一、中間件的概念
為了理解中間件,讓我們站在框架作者的角度思考問題:如果要添加功能,你會在哪個環節添加?
(1)Reducer:純函數,只承擔計算 State 的功能,不合適承擔其他功能,也承擔不了,因為理論上,純函數不能進行讀寫操作。 (2)View:與 State 一一對應,可以看作 State 的視覺層,也不合適承擔其他功能。 (3)Action:存放數據的對象,即消息的載體,只能被別人操作,自己不能進行任何操作。
想來想去,只有發送 Action 的這個步驟,即store.dispatch()
方法,可以添加功能。舉例來說,要添加日志功能,把 Action 和 State 打印出來,可以對store.dispatch
進行如下改造。
let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); next(action); console.log('next state', store.getState()); }
上面代碼中,對store.dispatch
進行了重定義,在發送 Action 前后添加了打印功能。這就是中間件的雛形。
中間件就是一個函數,對store.dispatch
方法進行了改造,在發出 Action 和執行 Reducer 這兩步之間,添加了其他功能。
個人理解:
將具體業務和底層邏輯解耦的組件。
大致的效果是:
需要利用服務的人(前端寫業務的),不需要知道底層邏輯(提供服務的)的具體實現,只要拿着中間件結果來用就好了。舉個例子:
然而我只想好好做炸雞,有性價比高的肉送來就行。於是我找到了一個專門整合屠雞場資源的第三方代理(中間件),跟他談好價格和質量后(統一接口),從今天開始,我就只需要給代理錢,然后拿肉就行。代理負責保證肉的質量,至於如何根據實際性價比,選擇不同的屠雞場,那就是代理做的事了。
我開了一家炸雞店(業務端),然而周邊有太多屠雞場(底層),為了成本我肯定想一個個比價,再綜合質量挑選一家屠雞場合作(適配不同底層邏輯)。由於市場變化,合作一段時間后,或許性價比最高的屠雞場就不是我最開始選的了,我又要重新和另一家屠雞場合作,進貨方式、交易方式等等全都要重來一套(重新適配)。
即中間件實際上就是在某兩個步驟之間承擔一部分任務,完成某個功能,這就是中間件。
二、 中間件的用法
本教程不涉及如何編寫中間件,因為常用的中間件都有現成的,只要引用別人寫好的模塊即可。比如,上一節的日志中間件,就有現成的redux-logger模塊。這里只介紹怎么使用中間件。
import { applyMiddleware, createStore } from 'redux'; import createLogger from 'redux-logger'; const logger = createLogger(); const store = createStore( reducer, applyMiddleware(logger) );
即先從redux中import使用中間件的插件,然后就可以在創建store的時候使用中間件了。 當然,中間件也是需要提前引入的。
上面代碼中,redux-logger
提供一個生成器createLogger
,可以生成日志中間件logger
。然后,將它放在applyMiddleware
方法之中,傳入createStore
方法,就完成了store.dispatch()
的功能增強。
補充:我們怎么知道 redux 模塊是否含有 applyMiddleware 和 createStore模塊呢? 我們可以在 npm install redux --save 之后在node-modules中找到redux,然后在redux中找到入口文件,接着進一步找到內層文件,找到 export , 我們在es/utils/index.js中可以看到導出文件如下:
export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose };
這樣,引入applyMiddleware中間件就沒有任何問題了。
需要注意的是:(1)createStore
方法可以接受整個應用的初始狀態作為參數,那樣的話,applyMiddleware
就是第三個參數了。
const store = createStore( reducer, initial_state, applyMiddleware(logger) );
(2)中間件的次序有講究。
const store = createStore( reducer, applyMiddleware(thunk, promise, logger) );
上面代碼中,applyMiddleware
方法的三個參數,就是三個中間件。有的中間件有次序要求,使用前要查一下文檔。比如,logger
就一定要放在最后,否則輸出結果會不正確。
三、applyMiddlewares()
上面我們使用了applyMiddlewares()方法,這個方法的作用就是使用中間件,之前,我們也已經找到其源碼所在位置,現在粘貼如下所示:
export default function applyMiddleware() { for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) { middlewares[_key] = arguments[_key]; } return function (createStore) { return function (reducer, preloadedState, enhancer) { var store = createStore(reducer, preloadedState, enhancer); var _dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: function dispatch(action) { return _dispatch(action); } }; chain = middlewares.map(function (middleware) { return middleware(middlewareAPI); }); _dispatch = compose.apply(undefined, chain)(store.dispatch); return _extends({}, store, { dispatch: _dispatch }); }; }; }
即這里可以接受任意多的中間件,然后將所有中間件放在一個middlewares數組中。 接着返回一個函數,使用中間件,在阮一峰老師的博客中也有此段源代碼,寫法有不同之處,只是這里是es5他的是es6的語法。
四、異步操作的基本思路
理解了中間件以后,就可以處理異步操作了。
同步操作只要發出一種 Action 即可,異步操作的差別是它要發出三種 Action。
操作發起時的 Action
操作成功時的 Action
操作失敗時的 Action
以向服務器取出數據為例,三種 Action 可以有兩種不同的寫法。
// 寫法一:名稱相同,參數不同 { type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Oops' } { type: 'FETCH_POSTS', status: 'success', response: { ... } } // 寫法二:名稱不同 { type: 'FETCH_POSTS_REQUEST' } { type: 'FETCH_POSTS_FAILURE', error: 'Oops' } { type: 'FETCH_POSTS_SUCCESS', response: { ... } }
除了 Action 種類不同,異步操作的 State 也要進行改造,反映不同的操作狀態。下面是 State 的一個例子。
let state = { // ... isFetching: true, didInvalidate: true, lastUpdated: 'xxxxxxx' };
五、 redux-thunk 中間件
這個中間件就可以解決異步actions的多次action觸發問題。
六、 redux-promise中間件
既然 Action Creator 可以返回函數,當然也可以返回其他值。另一種異步操作的解決方案,就是讓 Action Creator 返回一個 Promise 對象。
即此中間件解決的問題和redux-thunk中間件解決的問題相似。