redux中间件原理-讲义
1、redux中間件簡介
1.1、什么是redux中間件
redux 提供了類似后端 Express 的中間件概念,本質的目的是提供第三方插件的模式,自定義攔截 action -> reducer 的過程。變為 action -> middlewares -> reducer 。這種機制可以讓我們改變數據流,實現如異步 action ,action 過濾,日志輸出,異常報告等功能。
通俗來說,redux中間件就是對dispatch的功能做了擴展。
先來看一下傳統的redux執行流程:
圖1 redux傳統執行流程
代碼示例: import { createStore } from 'redux';/*** 這是一個 reducer,形式為 (state, action) => state 的純函數。* 描述了 action 如何把 state 轉變成下一個 state。*/ function counter(state = 0, action) {switch (action.type) {case 'INCREMENT':return state + 1;case 'DECREMENT':return state - 1;default:return state;} }// 創建 Redux store 來存放應用的狀態。 // API 是 { subscribe, dispatch, getState }。 let store = createStore(counter);// 可以手動訂閱更新,也可以事件綁定到視圖層。 store.subscribe(() =>console.log(store.getState()) );// 改變內部 state 惟一方法是 dispatch 一個 action。 // action 可以被序列化,用日記記錄和儲存下來,后期還可以以回放的方式執行 store.dispatch({ type: 'INCREMENT' }); // 1 store.dispatch({ type: 'INCREMENT' }); // 2 store.dispatch({ type: 'DECREMENT' }); // 1Redux的核心概念其實很簡單:將需要修改的state都存入到store里,發起一個action用來描述發生了什么,用reducers描述action如何改變state tree 。創建store的時候需要傳入reducer,真正能改變store中數據的是store.dispatch API。
對dispatch改造后,效果如下:
圖2 dispatch改造后的執行流程
如上圖所示,dispatch派發給 redux Store 的 action 對象,到達reducer之前,進行一些額外的操作,會被 Store 上的多個中間件依次處理。例如可以利用中間件來進行日志記錄、創建崩潰報告、調用異步接口或者路由等等,那么其實所有的對 action 的處理都可以有中間件組成的。 簡單來說,中間件就是對store.dispatch()的增強。1.2、使用redux中間件
redux有很多中間件,我們這里以 redux-thunk 為例。
代碼示例:
import { applyMiddleware, createStore } from 'redux'; import thunk from 'redux-thunk';const store = createStore(reducers, applyMiddleware(thunk) );直接將thunk中間件引入,放在applyMiddleware方法之中,傳入createStore方法,就完成了store.dispatch()的功能增強。即可以在reducer中進行一些異步的操作。
Redux middleware 提供了一個分類處理 action 的機會。在 middleware 中,我們可以檢閱每一個流過的 action,并挑選出特定類型的 action 進行相應操作,以此來改變 action。其實applyMiddleware就是Redux的一個原生方法,將所有中間件組成一個數組,依次執行。
中間件多了可以當做參數依次傳進去。
代碼示例:
import { applyMiddleware, createStore } from 'redux'; import thunk from 'redux-thunk'; import createLogger from 'redux-logger';const logger = createLogger();const store = createStore(reducers, applyMiddleware(thunk, logger) //會按順序執行 );2、中間件的運行機制
2.1、createStore源碼分析
源碼:
// 摘至createStore export function createStore(reducer, rootState, enhance) {//...if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}/*若使用中間件,這里 enhancer 即為 applyMiddleware()若有enhance,直接返回一個增強的createStore方法,可以類比成react的高階函數*/return enhancer(createStore)(reducer, preloadedState)}//... }對于createStore的源碼我們只需要關注和applyMiddleware有關的地方, 通過源碼得知在調用createStore時傳入的參數進行一個判斷,并對參數做矯正。 據此可以得出createStore有多種使用方法,根據第一段參數判斷規則,我們可以得出createStore的兩種使用方式:
const store = createStore(reducer, {a: 1, b: 2}, applyMiddleware(...));或:
const store = createStore(reducer, applyMiddleware(...));經過createStore中的第一個參數判斷規則后,對參數進行了校正,得到了新的enhancer得值,如果新的enhancer的值不為undeifined,便將createStore傳入enhancer(即applyMiddleware調用后返回的函數)內,讓enhancer執行創建store的過程。也就時說這里的:
enhancer(createStore)(reducer, preloadedState);實際上等同于:
applyMiddleware(mdw1, mdw2, mdw3)(createStore)(reducer, preloadedState);applyMiddleware會有兩層柯里化,同時表明它還有一種很函數式編程的用法,即 :
const store = applyMiddleware(mdw1, mdw2, mdw3)(createStore);這種方式將創建store的步驟完全放在了applyMiddleware內部,并在其內第二層柯里化的函數內執行創建store的過程即調用createStore,調用后程序將跳轉至createStore走參數判斷流程最后再創建store。
無論哪一種執行createStore的方式,我們都終將得到store,也就是在creaeStore內部最后返回的那個包含dispatch、subscribe、getState等方法的對象。
2.2、applyMiddleware源碼分析
源碼:
export default function applyMiddleware(...middlewares) {return createStore => (...args) => {// 利用傳入的createStore和reducer和創建一個storeconst store = createStore(...args)let dispatch = () => {throw new Error()}const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)}// 讓每個 middleware 帶著 middlewareAPI 這個參數分別執行一遍const chain = middlewares.map(middleware => middleware(middlewareAPI))// 接著 compose 將 chain 中的所有匿名函數,組裝成一個新的函數,即新的 dispatchdispatch = compose(...chain)(store.dispatch)return {...store,dispatch}} }為方便閱讀和理解,部分ES6箭頭函數已修改為ES5的普通函數形式,如下:
function applyMiddleware (...middlewares){return function (createStore){return function (reducer, preloadedState, enhancer){const store = createStore(reducer, preloadedState, enhancer);let dispatch = function (){throw new Error()};const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)};//一下兩行代碼是所有中間件被串聯起來的核心部分實現// 讓每個 middleware 帶著 middlewareAPI 這個參數分別執行一遍const chain = middlewares.map(middleware => middleware(middlewareAPI));// 接著 compose 將 chain 中的所有匿名函數,組裝成一個新的函數,即新的 dispatchdispatch = compose(...chain)(store.dispatch);return {...store,dispatch};}} }從上面的代碼我們不難看出,applyMiddleware 這個函數的核心就在于在于組合 compose,通過將不同的 middlewares 一層一層包裹到原生的 dispatch 之上,然后對 middleware 的設計采用柯里化的方式,以便于compose ,從而可以動態產生 next 方法以及保持 store 的一致性。
在函數式編程(Functional Programming)相關的文章中,經常能看到 柯里化(Currying)這個名詞。它是數學家柯里(Haskell Curry)提出的。
柯里化,用一句話解釋就是,把一個多參數的函數轉化為單參數函數的方法。
根據源碼,我們可以將其主要功能按步驟劃分如下:
1、依次執行middleware
將middleware執行后返回的函數合并到一個chain數組,這里我們有必要看看標準middleware的定義格式,如下:
const chain = middlewares.map(middleware => middleware(middlewareAPI));遍歷所有的中間件,并調用它們,傳入那個類似于store的對象middlewareAPI,這會導致中間件中第一層柯里化函數被調用,并返回一個接收next(即dispatch)方法作為參數的新函數。
export default store => next => action => {}// 即 function (store) {return function(next) {return function (action) {return {}}} }那么此時合并的chain結構如下:
[ ...,function(next) {return function (action) {return {}}} ]2、改變dispatch指向
dispatch = compose(...chain)(store.dispatch);我們展開了這個數組,并將其內部的元素(函數)傳給了compose函數,compose函數又返回了我們一個新函數。然后我們再調用這個新函數并傳入了原始的未經任何修改的dispatch方法,最后返回一個經過了修改的新的dispatch方法。
什么是compose?在函數式編程中,compose指接收多個函數作為參數,并返回一個新的函數的方式。調用新函數后傳入一個初始的值作為參數,該參數經最后一個函數調用,將結果返回并作為倒數第二個函數的入參,倒數第二個函數調用完后,將其結果返回并作為倒數第三個函數的入參,依次調用,知道最后調用完傳入compose的所有的函數后,返回一個最后的結果。
compose函數如下:
[...chain].reduce((a, b) => (...args) => a(b(...args)))
實際就是一個柯里化函數,即將所有的middleware合并成一個middleware,并在最后一個middleware中傳入當前的dispatch。
調用compose(...chain)(store.dispatch)后返回a(b(c(dispatch)))。
可以發現已經將所有middleware串聯起來了,并同時修改了dispatch的指向。
最后看一下這時候compose執行返回,如下:
總結來說就是:
在中間件串聯的時候,middleware1-3的串聯順序是從右至左的,也就是middleware3被包裹在了最里面,它內部含有對原始的store.dispatch的調用,middleware1被包裹在了最外邊。
當我們在業務代碼中dispatch一個action時,也就是中間件執行的時候,middleware1-3的執行順序是從左至右的,因為最后被包裹的中間件,將被最先執行。
如圖所示:
3、常見的redux中間件
3.1、logger日志中間件
源碼:
function createLogger(options = {}) {/*** 傳入 applyMiddleWare 的函數* @param {Function} { getState }) [description]* @return {[type]} [description]*/return ({ getState }) => (next) => (action) => {let returnedValue;const logEntry = {};logEntry.prevState = stateTransformer(getState());logEntry.action = action;// .... returnedValue = next(action);// ....logEntry.nextState = stateTransformer(getState());// ....return returnedValue;}; }export default createLogger;為了方便查看,將代碼修改為ES5之后,如下:
/*** getState 可以返回最新的應用 store 數據*/ function ({getState}) {/*** next 表示執行后續的中間件,中間件有可能有多個*/return function (next) {/*** 中間件處理函數,參數為當前執行的 action */return function (action) {...}} }這樣的結構本質上就是為了將 middleware 串聯起來執行。
3.2、redux異步管理中間件
在多種中間件中,處理 redux 異步事件的中間件,絕對占有舉足輕重的地位。從簡單的 react-thunk 到 redux-promise 再到 redux-saga等等,都代表這各自解決redux異步流管理問題的方案。
3.2.1、redux-thunk
redux-thunk的使用:
function getWeather(url, params) {return (dispatch, getState) => {fetch(url, params).then(result => {dispatch({type: 'GET_WEATHER_SUCCESS', payload: result,});}).catch(err => {dispatch({type: 'GET_WEATHER_ERROR', error: err,});});}; }在上述使用實例中,我們應用thunk中間到redux后,可以dispatch一個方法,在方法內部我們想要真正dispatch一個action對象的時候再執行dispatch即可,特別是異步操作時非常方便。
源碼:
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;為了方便閱讀,源碼中的箭頭函數在這里換成了普通函數,如下:
function createThunkMiddleware (extraArgument){return function ({dispatch, getState}){return function (next){return function (action){if (typeof action === 'function'){return action(dispatch, getState, extraArgument);}return next(action);};}} }let thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware;export default thunk;thunk是一個很常用的redux中間件,應用它之后,我們可以dispatch一個方法,而不僅限于一個純的action對象。它的源碼也很簡單,如上所示,除去語法固定格式也就區區幾行。
下面我們就來看看源碼(為了方便閱讀,源碼中的箭頭函數在這里換成了普通函數),首先是這三層柯里化:
// 外層 function createThunkMiddleware (extraArgument){// 第一層return function ({dispatch, getState}){// 第二層return function (next){// 第三層return function (action){if (typeof action === 'function'){return action(dispatch, getState, extraArgument);}return next(action);};}} }首先是外層,上面的源碼可知,這一層存在的主要目的是支持在調用applyMiddleware并傳入thunk的時候時候可以不直接傳入thunk本身,而是先調用包裹了thunk的函數(第一層柯里化的父函數)并傳入需要的額外參數,再將該函數調用的后返回的值(也就是真正的thunk)傳給applyMiddleware,從而實現對額外參數傳入的支持,使用方式如下:
const store = createStore(reducer, applyMiddleware(thunk.withExtraArgument({api, whatever})));如果無需額外參數則用法如下:
const store = createStore(reducer, applyMiddleware(thunk));接下來來看第一層,這一層是真正applyMiddleware能夠調用的一層,從形參來看,這個函數接收了一個類似于store的對象,因為這個對象被結構以后獲取了它的dispatch和getState這兩個方法,巧的是store也有這兩方法,但這個對象到底是不是store,還是只借用了store的這兩方法合成的一個新對象?這個問題在我們后面分析applyMiddleware源碼時,自會有分曉。
再來看第二層,在第二層這個函數中,我們接收的一個名為next的參數,并在第三層函數內的最后一行代碼中用它去調用了一個action對象,感覺有點 dispatch({type: ‘XX_ACTION’, data: {}}) 的意思,因為我們可以懷疑它就是一個dispatch方法,或者說是其他中間件處理過的dispatch方法,似乎能通過這行代碼鏈接上所有的中間件,并在所有只能中間件自身邏輯處理完成后,最終調用真實的store.dispath去dispatch一個action對象,再走到下一步,也就是reducer內。
最后我們看看第三層,在這一層函數的內部源碼中首先判斷了action的類型,如果action是一個方法,我們就調用它,并傳入dispatch、getState、extraArgument三個參數,因為在這個方法內部,我們可能需要調用到這些參數,至少dispatch是必須的。**這三行源碼才是真正的thunk核心所在。所有中間件的自身功能邏輯也是在這里實現的。**如果action不是一個函數,就走之前解析第二層時提到的步驟。
3.2.2、redux-promise
不同的中間件都有著自己的適用場景,react-thunk 比較適合于簡單的API請求的場景,而 Promise 則更適合于輸入輸出操作,比較fetch函數返回的結果就是一個Promise對象,下面就讓我們來看下最簡單的 Promise 對象是怎么實現的:
import { isFSA } from 'flux-standard-action';function isPromise(val) {return val && typeof val.then === 'function'; }export default function promiseMiddleware({ dispatch }) {return next => action => {if (!isFSA(action)) {return isPromise(action)? action.then(dispatch): next(action);}return isPromise(action.payload)? action.payload.then(result => dispatch({ ...action, payload: result }),error => {dispatch({ ...action, payload: error, error: true });return Promise.reject(error);}): next(action);}; }它的邏輯也很簡單主要是下面兩部分:
結合 redux-promise 我們就可以利用 es7 的 async 和 await 語法,來簡化異步操作了,比如這樣:
const fetchData = (url, params) => fetch(url, params) async function getWeather(url, params) {const result = await fetchData(url, params)if (result.error) {return {type: 'GET_WEATHER_ERROR', error: result.error,}}return {type: 'GET_WEATHER_SUCCESS', payload: result,}}3.2.3、redux-saga
redux-saga是一個管理redux應用異步操作的中間件,用于代替 redux-thunk 的。它通過創建 Sagas 將所有異步操作邏輯存放在一個地方進行集中處理,以此將react中的同步操作與異步操作區分開來,以便于后期的管理與維護。對于Saga,我們可簡單定義如下:
Saga = Worker + Watcher
redux-saga相當于在Redux原有數據流中多了一層,通過對Action進行監聽,從而捕獲到監聽的Action,然后可以派生一個新的任務對state進行維護(這個看項目本身的需求),通過更改的state驅動View的變更。如下圖所示:
saga特點:
場景。
6. 提供 channel 機制支持外部事件。
總的來講Redux Saga適用于對事件操作有細粒度需求的場景,同時它也提供了更好的可測試性,與可維護性,比較適合對異步處理要求高的大型項目,而小而簡單的項目完全可以使用redux-thunk就足以滿足自身需求了。畢竟react-thunk對于一個項目本身而言,毫無侵入,使用極其簡單,只需引入這個中間件就行了。而react-saga則要求較高,難度較大。
總結
以上是生活随笔為你收集整理的redux中间件原理-讲义的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python3-numpy数组切片和索引
- 下一篇: Windows批处理文件(.bat文件和