redux功能強(qiáng)大的Middleware中間件使用學(xué)習(xí)
引言
上一節(jié)我們學(xué)習(xí)了redux在實(shí)際項目的應(yīng)用細(xì)節(jié),這一節(jié)我們來學(xué)習(xí)redux中一個很重要的概念:中間件。我們會簡單實(shí)現(xiàn)一個記錄的中間件, 然后學(xué)習(xí)redux-saga這個異步請求中間件。
redux中的Middleware
redux中的中間件提供的是位于 action 被發(fā)起之后,到達(dá) reducer 之前的擴(kuò)展點(diǎn)。 你可以利用 Redux middleware 來進(jìn)行日志記錄、創(chuàng)建崩潰報告、調(diào)用異步接口或者路由等等。
記錄日志
試想一下,如果我們的redux在每一次dispatch的時候都可以記錄下此次發(fā)生的action以及dispatch結(jié)束后的store。那么在我們的應(yīng)用 出現(xiàn)問題的時候,我們就可以輕松的查閱日志找出是哪個action導(dǎo)致了state不正確。那么我們怎樣通過redux實(shí)現(xiàn)它呢?
手動記錄
最直接的解決方案就是在每次調(diào)用 store.dispatch(action)
前后手動記錄被發(fā)起的 action 和新的 state。假如你在創(chuàng)建一個action時這樣調(diào)用:
store.dispatch(addTodo('use Redux'))
為了記錄這個 action 以及產(chǎn)生的新的 state,你可以通過這種方式記錄日志:
let action = addTodo('Use Redux') console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState())
那么很自然的就能想到可以封裝為一個函數(shù)在各處調(diào)用:
function dispatchAndLog(store, action) { console.log('dispatching', action) store.dispatch(action) console.log('next state', store.getState()) } dispatchAndLog(store, addTodo('Use Redux'))
但是這樣我們還是需要每次導(dǎo)入一個外部方法,那么如果我們直接去替換store實(shí)例中的dispatch函數(shù)呢?
let next = store.dispatch store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result }
其實(shí)到這里我們想要實(shí)現(xiàn)的功能已經(jīng)完成了,但是距離Middleware實(shí)際使用的方法還是有不小的差距, 同時我們這里只能對dispatch的擴(kuò)展時十分有限的,如果我想對其添加其他的功能,又該怎么實(shí)現(xiàn)呢? 首先可以確定的是我們需要將每一個功能分離開來,我們希望的時一個功能對應(yīng)一個模塊,那么當(dāng)我們想添加其他的模塊時,應(yīng)該是這樣的:
function patchStoreToAddLogging(store) { let next = store.dispatch store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } } // 崩潰報告模塊 function patchStoreToAddCrashReporting(store) { let next = store.dispatch store.dispatch = function dispatchAndReportErrors(action) { try { return next(action) } catch (err) { console.error('捕獲一個異常!', err) Raven.captureException(err, { extra: { action, state: store.getState() } }) throw err } } }
然后我們可以在store中使用它們:
patchStoreToAddLogging(store) patchStoreToAddCrashReporting(store)
那么有沒有一種更好的代碼組織方式呢?此前,我們使用dispatchAndLog
替換了dispatch
, 如果我們不這樣做,而是在函數(shù)中返回新的dispatch
呢?
function logger(store) { let next = store.dispatch // 我們之前的做法: // store.dispatch = function dispatchAndLog(action) { return function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } }
然后我們在外部提供方法將它替換到store.dispatch
中。
function applyMiddlewareByMonkeypatching(store, middlewares) { middlewares = middlewares.slice() middlewares.reverse() // 在每一個 middleware 中變換 dispatch 方法。 middlewares.forEach(middleware => store.dispatch = middleware(store) ) }
其實(shí)到了這里我們的中間件功能已經(jīng)大體實(shí)現(xiàn),如果想后續(xù)繼續(xù)深入請參考redux官方文檔
redux-saga
接下來我們來看管理應(yīng)用程序副作用的中間件reudx-saga。他在redux中有很多使用場景,但是我們使用最多的還是用它來進(jìn)行網(wǎng)絡(luò)請求。
redux-saga使用了ES6的Generator功能,讓異步的流程更易于讀取,寫入和測試。因此我們首先了解一下generator函數(shù)是什么?
Generator函數(shù)
形式上,Generator函數(shù)是一個普通函數(shù),但是有兩個特征。
一是,function關(guān)鍵字與函數(shù)名之間有一個星號;
二是,函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài).
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } const hw = helloWorldGenerator();
Generator 函數(shù)的調(diào)用方法與普通函數(shù)一樣,也是在函數(shù)名后面加上一對圓括號。不同的是,調(diào)用 Generator 函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運(yùn)行結(jié)果,而是一個指向內(nèi)部狀態(tài)的指針對象。
下一步,必須調(diào)用遍歷器對象的next方法,使得指針移向下一個狀態(tài)。也就是說,每次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或上一次停下來的地方開始執(zhí)行,直到遇到下一個yield表達(dá)式(或return語句)為止。
換言之,Generator 函數(shù)是分段執(zhí)行的,yield表達(dá)式是暫停執(zhí)行的標(biāo)記,而next方法可以恢復(fù)執(zhí)行
hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
實(shí)際使用場景
現(xiàn)在來看一個項目中的實(shí)際使用
//查詢搜索列表 const requestLists = function*({ page, keyword, callback }) { try { appLoading(); // 展現(xiàn)加載框 const body = { keyword: keyword, page: page, size: size, }; const result = yield handleData.get(DataUrls.searchLists, body) // 發(fā)送網(wǎng)絡(luò)請求 if (result && result.success) { yield put(Action.fetchSearchListDone(lists)); // 請求成功保存數(shù)據(jù) callback && callback(); } else { showModal((result && result.message) || '系統(tǒng)繁忙,請稍后'); yield put(Action.fetchSearchListFailure(result)); //請求失敗錯誤處理 } } catch (err) { yield put(Action.fetchSearchListFailure(err)); //錯誤處理 showModal('系統(tǒng)繁忙,請稍后'); } finally { appFinish(); //關(guān)閉加載框 } };
上面是一個比較完整的請求處理過程,從發(fā)送請求到成功或失敗處理都有包含到。
對上面的代碼做一個解釋,在這個函數(shù)中我們首先使用yield發(fā)起一個異步請求,這時middleware 會暫停 Saga,直到請求完成。 一旦完成后,不管是成功或者失敗,middleware 會恢復(fù) Saga 接著執(zhí)行,直到遇到下一個 yield。當(dāng) try 報錯時, 會執(zhí)行到catch去捕獲異常, 在這里遇到下一個yield,調(diào)用請求失敗的Action,傳入失敗原因。請求成功時遇到下一個 yield 對象,調(diào)用請求成功的Action,傳入結(jié)果。
put 就是我們稱作副作用的一個例子。副作用是一些簡單 Javascript 對象,包含了要被 middleware 執(zhí)行的指令。 當(dāng) middleware 拿到一個被 Saga yield 的副作用,它會暫停 Saga,直到副作用執(zhí)行完成,然后 Saga 會再次被恢復(fù)。
接下來我們需要去啟動這個saga,為了做到這一點(diǎn),我們將添加一個 listSaga,負(fù)責(zé)啟動其他的 Sagas。在同一個文件中:
const listSagas = function* listSagas() { yield all([ takeEvery('LIST_REQUESTLIST', requestLists), ]); }; export default listSagas;
其中的輔助函數(shù)takeEvery
用于監(jiān)聽所有的LIST_REQUESTLIST
action,在action執(zhí)行的時候去啟動相應(yīng)的requestLists
任務(wù)。 定義一個listSagas
的原因就是我們這個文件中可能遠(yuǎn)不至這一個副作用函數(shù),當(dāng)定義了多個的時候,我們可以在all中添加一個takeEvery, 這樣就會有兩個Generators同時啟動。在實(shí)際項目中因為項目所分的模塊可能會有很多,因此對每個模塊都定義一個sagas是很有必要的, 最終在sagas的最外層定義一個index.js
文件用來將我們的所有模塊整合在一起定義一個root
,然后我們只有在 main.js
的 root Saga 中調(diào)用sagaMiddleware.run
。就可以啟動所有的sagas。
對于其他更加詳細(xì)的redux-saga學(xué)習(xí)可以參考文檔
以上就是redux功能強(qiáng)大的Middleware中間件使用學(xué)習(xí)的詳細(xì)內(nèi)容,更多關(guān)于redux中間件Middleware的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在react中使用highlight.js將頁面上的代碼高亮的方法
本文通過 highlight.js 庫實(shí)現(xiàn)對文章正文 HTML 中的代碼元素自動添加語法高亮,具有一定的參考價值,感興趣的可以了解一下2022-01-01React不使用requestIdleCallback實(shí)現(xiàn)調(diào)度原理解析
這篇文章主要為大家介紹了React不使用requestIdleCallback實(shí)現(xiàn)調(diào)度原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11react中useState使用:如何實(shí)現(xiàn)在當(dāng)前表格直接更改數(shù)據(jù)
這篇文章主要介紹了react中useState的使用:如何實(shí)現(xiàn)在當(dāng)前表格直接更改數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08一文詳解手動實(shí)現(xiàn)Recoil狀態(tài)管理基本原理
這篇文章主要為大家介紹了一文詳解手動實(shí)現(xiàn)Recoil狀態(tài)管理基本原理實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05React和Vue實(shí)現(xiàn)文件下載進(jìn)度條
本文主要介紹了React和Vue實(shí)現(xiàn)文件下載進(jìn)度條,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04React 遠(yuǎn)程動態(tài)組件實(shí)踐示例詳解
這篇文章主要為大家介紹了React 遠(yuǎn)程動態(tài)組件實(shí)踐示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03在React框架中實(shí)現(xiàn)一些AngularJS中ng指令的例子
這篇文章主要介紹了在JavaScript的React框架中實(shí)現(xiàn)一些AngularJS指令的例子,React使用Virtual DOM因而與普通的js框架有些不同,需要的朋友可以參考下2016-03-03react+antd實(shí)現(xiàn)動態(tài)編輯表格數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了react+antd實(shí)現(xiàn)動態(tài)編輯表格數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08關(guān)于react-router-dom路由入門教程
這篇文章主要介紹了關(guān)于react-router-dom路由入門教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03