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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

React学习笔记——redux里中间件Middleware的运行机理

發布時間:2023/12/14 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 React学习笔记——redux里中间件Middleware的运行机理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1、前言

上篇文章中,我們詳細介紹了redux的相關知識和如何使用,最后使用中間件Middleware來幫助我們完成異步操作,如下圖

上面是很典型的一次 redux 的數據流的過程,在增加了 middleware 后,我們就可以在這途中對 action 進行截獲,并進行改變,進行其他操作。

同時,在使用 middleware 時,我們可以通過串聯不同的 middleware 來滿足日常的開發,每一個 middleware 都可以處理一個相對獨立的業務需求且相互串聯。

如上圖所示,派發給 redux Store 的 action 對象,會被 Store 上的多個中間件依次處理,如果把 action 和當前的 state 交給 reducer 處理的過程看做默認存在的中間件,那么其實所有的對 action 的處理都可以有中間件組成的。值得注意的是這些中間件會按照指定的順序一次處理傳入的 action,只有排在前面的中間件完成任務之后,后面的中間件才有機會繼續處理 action,同樣的,每個中間件都有自己的“熔斷”處理,當它認為這個 action 不需要后面的中間件進行處理時,后面的中間件也就不能再對這個 action 進行處理了

下面我們來研究研究Middleware。

2、正文

2.1、redux-thunk源碼

我們以redux-thunk為例,從node_modules文件夾下面找到redux-thunk文件夾,查看其源碼(下圖為redux-thunk源碼,一共12行)

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;

可以看出,thunk是createThunkMiddleware()運行的結果,而該函數里面還包裹了3層函數(柯里化),函數一層一層向下執行。

我們將其中的ES6的箭頭函數換成普通函數,再觀察

function createThunkMiddleware (extraArgument){// 第一層/* getState 可以返回最新的應用 store 數據 */return function ({dispatch, getState}){// 第二層/* next 表示執行后續的中間件,中間件有可能有多個 */return function (next){// 第三層/*中間件處理函數,參數為當前執行的 action */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最后兩行源碼可知,這一層存在的主要目的是支持在調用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不是一個函數,就走之前解析第二層時提到的步驟。

2.2、ApplyMiddleware源碼

applyMiddleware函數共十來行代碼,這里將其完整復制出來。

import compose from './compose'export default function applyMiddleware(...middlewares) {return (createStore) => (...args) => {const store = createStore(...args)let dispatch = () => {throw new Error('Dispatching while constructing your middleware is not allowed. ' +'Other middleware would not be applied to this dispatch.')}const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args),}// 1、將store對象的基本方法傳遞給中間件并依次調用中間件const chain = middlewares.map((middleware) => middleware(middlewareAPI))// 2、改變dispatch指向,并將最初的dispatch傳遞給composedispatch = compose(...chain)(store.dispatch)return {...store,dispatch,}} }

同樣,我們將applyMiddleware的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('Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.')};const middlewareAPI = {getState: store.getState,dispatch: (...args) => dispatch(...args)};// 1、將store對象的基本方法傳遞給中間件并依次調用中間件const chain = middlewares.map(middleware => middleware(middlewareAPI));// 2、改變dispatch指向,并將最初的dispatch傳遞給composedispatch = compose(...chain)(store.dispatch);return {...store,dispatch};}} }

從其源碼可以看出,applyMiddleware內部一開始也是兩層柯里化,所以我們看看和applyMiddleware最有關系的createStore的主要源碼。

2.3、CreateStore源碼

在平時業務中,我們創建store時,一般這樣寫

const store = createStore(reducer,initial_state,applyMiddleware(···));

或者

const store = createStore(reducer, applyMiddleware(...));

所以我們也要關注createStore和applyMiddleware的源碼

createStore部分源碼:

// 摘至createStore export function createStore(reducer, preloadedState, enhancer) {...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)}............dispatch({ type: ActionTypes.INIT })return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable,} }

對于createStore的源碼我們只需要關注和applyMiddleware有關的地方。從其內部前面一部分代碼來看,其實很簡單,就是對調用createStore時傳入的參數進行一個判斷,并對參數做矯正,再決定以哪種方式來執行后續代碼。據此可以得出createStore有多種使用方法,根據第一段參數判斷規則,我們可以得出createStore的兩種使用方式:

const store = createStore(reducer, {a: 1, b: 2}, applyMiddleware(...));

以及

const store = createStore(reducer, applyMiddleware(...));
  • 根據第一段參數判斷規則,我們可以肯定的是: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.4、回看ApplyMiddleware源碼

對于applyMiddleware開頭的兩層柯里化的出現原因以及和createStore有關的方面,在前面分析過。同時,我們之前在redux-thunk里的第一層柯里化中猜測傳入的對象是一個類似于store的對象,通過上個章節中applyMiddleware的確實可以確認了。

這里我們主要討論中間件是如何通過applyMiddleware的工作起來并實現挨個串聯的。

接下來這幾段代碼是整個applyMiddleware的核心部分,也解釋了在第二章節中,我們對thunk中間件為啥有三層柯里化的疑慮

// ... // 1、將store對象的基本方法傳遞給中間件并依次調用中間件 const chain = middlewares.map(middleware => middleware(middlewareAPI)); // 2、改變dispatch指向,并將最初的dispatch傳遞給compose dispatch = compose(...chain)(store.dispatch);return {...store,dispatch }; // ...
  • 首先,我們可以直觀的看到,applyMiddleware的執行結果最終返回的是:store的所有方法和一個dispatch方法。
2.4.1、redux-thunk的第一層柯里化

這個dispatch方法是怎么來的呢?我們來看頭兩行代碼,這兩行代碼也是所有中間件被串聯起來的核心部分實現,它們也決定了中間件內部為啥會有我們在之前章節中提到的三層柯里化的固定格式,先看第一行代碼:

const chain = middlewares.map(middleware => middleware(middlewareAPI));
  • 遍歷所有的中間件,并調用它們,傳入那個類似于store的對象middlewareAPI,這會導致中間件(redux-thunk)中第一層柯里化函數被調用,并返回一個接收next(即dispatch)方法作為參數的新函數
  • 這一層柯里化主要原因,還是考慮到中間件內部會有調用store方法的需求,所以我們需要在此注入相關的方法,其內存函數可以通過閉包的方式來獲取并調用,若有需要的話
  • 遍歷結束以后,我們拿到了一個包含所有中間件新返回的函數的一個數組,將其賦值給變量chain,譯為函數鏈
2.4.2、redux-thunk的第二層柯里化

再來看第二句代碼:

dispatch = compose(...chain)(store.dispatch);
  • 我們展開了這個數組,并將其內部的元素(函數)傳給了compose函數,compose函數又返回了我們一個新函數。然后我們再調用這個新函數并傳入了原始的未經任何修改的dispatch方法,最后返回一個經過了修改的新的dispatch方法
  • 先說一句,compose是從右到左依次調用傳入其內部的函數鏈
  • thunk中間件的第二層柯里化函數即在compose內部被調用,并接收了經其右邊那個中間函數改造并返回dispatch方法作為入參,并返回一個新的函數,再在該函數內部添加自己的邏輯,最后調用右邊那個中間函數改造并返回dispatch方法接著執行前一個中間件的邏輯(當然如果只有一個thunk中間件被應用了,或者他出入傳入compose時的最后一個中間件,那么傳入的dispatch方法即為原始的store.dispatch方法)
2.4.3、redux-thunk的第三層柯里化

thunk的第三層柯里化函數,即為被thunk改造后的dispatch方法:

// ... return function (action){// thunk的內部邏輯if (typeof action === 'function'){return action(dispatch, getState, extraArgument);}// 調用經下一個中間件(在compose中為之前的中間件)改造后的dispatch方法(本層洋蔥殼的下一層),并傳入actionreturn next(action); }; // ...
  • 這個改造后的dispatch函數將通過compose傳入thunk左邊的那個中間件作為入參
2.4.4、總結

經上述分析,我們可以得出一個中間件的串聯和執行時的流程,以下面這段使用applyMiddleware的代碼為例:

export default createStore(reducer, applyMiddleware(middleware1, middleware2, middleware3));
  • 在applyMiddlware內部的compose串聯中間件時,順序是從右至左,就是先調用middleware3、再middleware2、最后middleware1
  • middleware3最開始接收真正的store.dispatch作為入參,并返回改造的的dispatch函數作為入參傳給middleware2,這個改造后的函數內部包含有對原始store.dispatch的調用。依次內推知道從右到左走完所有的中間件
  • 整個過程就像是給原始的store.dispatch方法套上了一層又一層的殼子,最后得到了一個類似于洋蔥結構的東西,也就是下面源碼中的dispatch,這個經過中間件改造并返回的dispatch方法將替換store被展開后的原始的dispatch方法:
// ... return {...store,dispatch }; // ...
  • 而原始的store.dispatch就像這洋蔥內部的芯,被覆蓋在了一層又一層的殼的最里面
  • 而當我們剝殼的時候,剝一層殼,執行一層的邏輯,即走一層中間件的功能,直至調用藏在最里邊的原始的store.dispatch方法去派發action。這樣一來我們就不需要在每次派發action的時候再寫單獨的代碼邏輯的

如上圖所示:

  • 在中間件串聯的時候,middleware1-3的串聯順序是從右至左的,也就是middleware3被包裹在了最里面,它內部含有對原始的store.dispatch的調用,middleware1被包裹在了最外邊
  • 在執行業務代碼中dispatch一個action時,也就是中間件執行的時候,middleware1-3的執行順序是從左至右的,因為最后被包裹的中間件,將被最先執行

2.5、總體流程

進過上述分析,我們可以將其主要功能按步驟劃分如下:

1、依次執行middleware:

將middleware執行后返回的函數合并到一個chain數組,這里我們有必要看看標準middleware的定義格式,如下

**加粗樣式**export default store => next => action => {}// 即 function (store) {return function(next) {return function (action) {return {}}} }

那么此時合并的chain結構如下

[ ...,function(next) {return function (action) {return {}}} ]

2、改變dispatch指向:

想必你也注意到了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)

本博客參考文章:

  • Redux的中間件原理分析
  • 十分鐘理解Redux中間件
  • 理解 redux 中間件
  • 詳解redux中間件

總結

以上是生活随笔為你收集整理的React学习笔记——redux里中间件Middleware的运行机理的全部內容,希望文章能夠幫你解決所遇到的問題。

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

主站蜘蛛池模板: 免费看成人av | 国产精品大屁股白浆一区 | 17c在线| 亚洲作爱网 | 亚洲69av| 波多野结衣在线观看视频 | 日本边添边摸边做边爱 | 国产欧美日韩精品在线观看 | 啪免费视频 | 成人午夜剧场视频网站 | 青青草超碰在线 | 国产日韩一区二区三区 | 91综合国产 | 男人的天堂久久久 | 老师的肉丝玉足夹茎 | 成人做爰www免费看视频网站 | 久久精品日韩无码 | 扒开腿揉捏花蒂h | 在线毛片网 | 奇米色播 | 午夜av片| 国产乱妇4p交换乱免费视频 | 欧美一区二区三区在线 | 日韩在线观看免费高清 | 91小宝寻花一区二区三区 | 很色的网站 | 黄色无遮挡网站 | 四级毛片 | 69国产视频| 国精产品一品二品国精品69xx | 日剧再来一次第十集 | 精品免费国产 | 两口子交换真实刺激高潮 | 黄色字幕网 | 人人射av | 欧美一区二区三区在线看 | 麻豆一区二区99久久久久 | 美梦视频大全在线观看高清 | 亚洲精品爱爱 | 97青青草 | 亚洲国产在 | 久热免费在线视频 | 日韩欧美自拍 | 国模私拍在线 | 狠狠爱免费视频 | 成人p站在线观看 | 蜜桃av久久久亚洲精品 | 日本黄色片一级 | 欧美一级欧美三级在线观看 | 中文在线а√在线 | 视频在线免费观看 | 日韩一级大片 | 国产午夜福利视频在线观看 | 欧美日韩在线免费播放 | 黄页免费网站 | 狠狠人妻久久久久久综合 | 精品国产欧美一区二区三区成人 | 日韩黄色在线 | 欧美成人黄色网 | 国产精品资源网站 | 日本免费黄视频 | 性高潮久久久久久久久 | 婷婷九月| 久久久国产精品一区二区三区 | 一级视频毛片 | 男人天堂网在线视频 | 福利精品在线 | 国产精品免费视频观看 | 超碰在线免费观看97 | 国产日韩片 | av色在线观看 | 欧美极品videos精品 | 国产骚b | 中文在线免费观看 | 一区两区小视频 | 黑人一区二区 | 日本91av | 欧美精品色哟哟 | 依人99 | 亚洲欧美日韩综合一区二区 | 一色av| 人妻无码久久一区二区三区免费 | 岛国精品在线观看 | 亚洲AV综合色区国产精品天天 | 伊人青青久 | 狐狸视频污 | 成人福利视频网 | 中文一级片 | 国产成人在线一区二区 | 黄色91免费版 | 国产自产在线视频 | 免费一级淫片aaa片毛片a级 | 中文字幕在线成人 | 亚洲欧美精品在线观看 | 青草青在线视频 | 免费在线日韩av | 久久精品人人 | 欧美经典一区二区三区 | 国产中文字幕乱人伦在线观看 |