日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

redux中间件原理-讲义

發布時間:2024/9/27 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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' }); // 1

Redux的核心概念其實很簡單:將需要修改的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。

// 假設chain如下: chain = [a: next => action => { console.log('第1層中間件') return next(action) }b: next => action => { console.log('第2層中間件') return next(action) }c: next => action => { console.log('根dispatch') return next(action) } ]

調用compose(...chain)(store.dispatch)后返回a(b(c(dispatch)))。
可以發現已經將所有middleware串聯起來了,并同時修改了dispatch的指向。
最后看一下這時候compose執行返回,如下:

dispatch = a(b(c(dispatch)))// 調用dispatch(action) // 執行循序 /*1. 調用 a(b(c(dispatch)))(action) __print__: 第1層中間件2. 返回 a: next(action) 即b(c(dispatch))(action)3. 調用 b(c(dispatch))(action) __print__: 第2層中間件4. 返回 b: next(action) 即c(dispatch)(action)5. 調用 c(dispatch)(action) __print__: 根dispatch6. 返回 c: next(action) 即dispatch(action)7. 調用 dispatch(action) */

總結來說就是:

在中間件串聯的時候,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);}; }

它的邏輯也很簡單主要是下面兩部分:

  • 先判斷是不是標準的 flux action。如果不是,那么判斷是否是 promise, 是的話就執行 action.then(dispatch),否則執行 next(action)。
  • 如果是, 就先判斷 payload 是否是 promise,如果是的話 payload.then 獲取數據,然后把數據作為 payload 重新 dispatch({ …action, payload: result}) ;不是的話就執行 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特點:

  • saga 的應用場景是復雜異步。
  • 可以使用 takeEvery 打印 logger(logger大法好),便于測試。
  • 提供 takeLatest/takeEvery/throttle 方法,可以便利的實現對事件的僅關注最近實踐還是關注每一次實踐的時間限頻。
  • 提供 cancel/delay 方法,可以便利的取消或延遲異步請求。
  • 提供 race(effects),[…effects] 方法來支持競態和并行場景。
  • 提供 channel 機制支持外部事件。
  • function *getCurrCity(ip) {const data = yield call('/api/getCurrCity.json', { ip })yield put({type: 'GET_CITY_SUCCESS', payload: data,}) } function * getWeather(cityId) {const data = yield call('/api/getWeatherInfo.json', { cityId })yield put({type: 'GET_WEATHER_SUCCESS', payload: data,}) } function loadInitData(ip) {yield getCurrCity(ip)yield getWeather(getCityIdWithState(state))yield put({type: 'GET_DATA_SUCCESS',}) }

    場景。
    6. 提供 channel 機制支持外部事件。

    function *getCurrCity(ip) {const data = yield call('/api/getCurrCity.json', { ip })yield put({type: 'GET_CITY_SUCCESS', payload: data,}) } function * getWeather(cityId) {const data = yield call('/api/getWeatherInfo.json', { cityId })yield put({type: 'GET_WEATHER_SUCCESS', payload: data,}) } function loadInitData(ip) {yield getCurrCity(ip)yield getWeather(getCityIdWithState(state))yield put({type: 'GET_DATA_SUCCESS',}) }

    總的來講Redux Saga適用于對事件操作有細粒度需求的場景,同時它也提供了更好的可測試性,與可維護性,比較適合對異步處理要求高的大型項目,而小而簡單的項目完全可以使用redux-thunk就足以滿足自身需求了。畢竟react-thunk對于一個項目本身而言,毫無侵入,使用極其簡單,只需引入這個中間件就行了。而react-saga則要求較高,難度較大。

    總結

    以上是生活随笔為你收集整理的redux中间件原理-讲义的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。