摘要:執(zhí)行完后,獲得數(shù)組,,它保存的對(duì)象是圖中綠色箭頭指向的匿名函數(shù),因?yàn)殚]包,每個(gè)匿名函數(shù)都可以訪(fǎng)問(wèn)相同的,即。是函數(shù)式編程中的組合,將中的所有匿名函數(shù),,組裝成一個(gè)新的函數(shù),即新的,當(dāng)新執(zhí)行時(shí),,從左到右依次執(zhí)行所以順序很重要。
前言
It provides a third-party extension point between dispatching an
action, and the moment it reaches the reducer.
這是 redux 作者 Dan 對(duì) middleware 的描述,middleware 提供了一個(gè)分類(lèi)處理 action 的機(jī)會(huì),在 middleware 中你可以檢閱每一個(gè)流過(guò)的 action,挑選出特定類(lèi)型的 action 進(jìn)行相應(yīng)操作,給你一次改變 action 的機(jī)會(huì)。
為什么 dispatch 需要 middleware上圖表達(dá)的是 redux 中一個(gè)簡(jiǎn)單的同步數(shù)據(jù)流動(dòng)場(chǎng)景,點(diǎn)擊 button 后,在回調(diào)中 dispatch 一個(gè) action,reducer 收到 action 后,更新 state 并通知 view 重新渲染。單向數(shù)據(jù)流,看著沒(méi)什么問(wèn)題。但是,如果需要打印每一個(gè) action 信息用來(lái)調(diào)試,就得去改 dispatch 或者 reducer 代碼,使其具有打印日志的功能;又比如點(diǎn)擊 button 后,需要先去服務(wù)器請(qǐng)求數(shù)據(jù),只有等拿到數(shù)據(jù)后,才能重新渲染 view,此時(shí)我們又希望 dispatch 或者 reducer 擁有異步請(qǐng)求的功能;再比如需要異步請(qǐng)求完數(shù)據(jù)后,打印一條日志,再請(qǐng)求數(shù)據(jù),再打印日志,再渲染...
面對(duì)多種多樣的業(yè)務(wù)需求,單純的修改 dispatch 或 reducer 的代碼顯然不具有普世性,我們需要的是可以組合的,自由插拔的插件機(jī)制,這一點(diǎn) redux 借鑒了 koa 里中間件的思想,koa 是用于構(gòu)建 web 應(yīng)用的 NodeJS 框架。另外 reducer 更關(guān)心的是數(shù)據(jù)的轉(zhuǎn)化邏輯,所以 redux 的 middleware 是為了增強(qiáng) dispatch 而出現(xiàn)的。
上面這張圖展示了應(yīng)用 middleware 后 redux 處理事件的邏輯,每一個(gè) middleware 處理一個(gè)相對(duì)獨(dú)立的業(yè)務(wù)需求,通過(guò)串聯(lián)不同的 middleware,實(shí)現(xiàn)變化多樣的的功能。那么問(wèn)題來(lái)了:
middleware 怎么寫(xiě)?
redux 是如何讓 middlewares 串聯(lián)并跑起來(lái)的?
四步理解 middleware 機(jī)制redux 提供了 applyMiddleware 這個(gè) api 來(lái)加載 middleware,為了方便理解,下圖將兩者的源碼放在一起進(jìn)行分析。
圖左邊是 logger,打印 action 的 middleware,圖右邊則是 applyMiddleware 的源碼,applyMiddleware 代碼雖然只有二十多行,卻非常精煉,接下來(lái)我們就分四步來(lái)深入解析這張圖。
redux 的代碼都是用 ES6/7 寫(xiě)的,所以不熟悉諸如 store => next => action => 或...state的童鞋,可以先學(xué)習(xí)下箭頭函數(shù),展開(kāi)運(yùn)算符。
Step. 1 函數(shù)式編程思想設(shè)計(jì) middleware
middleware 的設(shè)計(jì)有點(diǎn)特殊,是一個(gè)層層包裹的匿名函數(shù),這其實(shí)是函數(shù)式編程中的柯里化 curry,一種使用匿名單參數(shù)函數(shù)來(lái)實(shí)現(xiàn)多參數(shù)函數(shù)的方法。applyMiddleware 會(huì)對(duì) logger 這個(gè) middleware 進(jìn)行層層調(diào)用,動(dòng)態(tài)地對(duì) store 和 next 參數(shù)賦值。
柯里化的 middleware 結(jié)構(gòu)好處在于:
易串聯(lián),柯里化函數(shù)具有延遲執(zhí)行的特性,通過(guò)不斷柯里化形成的 middleware 可以累積參數(shù),配合組合( compose,函數(shù)式編程的概念,Step. 2 中會(huì)介紹)的方式,很容易形成 pipeline 來(lái)處理數(shù)據(jù)流。
共享store,在 applyMiddleware 執(zhí)行過(guò)程中,store 還是舊的,但是因?yàn)殚]包的存在,applyMiddleware 完成后,所有的 middlewares 內(nèi)部拿到的 store 是最新且相同的。
另外,我們可以發(fā)現(xiàn) applyMiddleware 的結(jié)構(gòu)也是一個(gè)多層柯里化的函數(shù),借助 compose , applyMiddleware 可以用來(lái)和其他插件一起加強(qiáng) createStore 函數(shù)。
import { createStore, applyMiddleware, compose } from "redux"; import rootReducer from "../reducers"; import DevTools from "../containers/DevTools"; const finalCreateStore = compose( // Middleware you want to use in development: applyMiddleware(d1, d2, d3), // Required! Enable Redux DevTools with the monitors you chose DevTools.instrument() )(createStore);
Step. 2 給 middleware 分發(fā) store
創(chuàng)建一個(gè)普通的 store 通過(guò)如下方式:
let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);
上面代碼執(zhí)行完后,applyMiddleware 函數(shù)陸續(xù)獲得了三個(gè)參數(shù),第一個(gè)是 middlewares 數(shù)組,[md1, mid2, mid3, ...],第二個(gè) next 是 Redux 原生的 createStore,最后一個(gè)是 reducer。接下來(lái)我們從對(duì)比圖中可以看到,applyMiddleware 利用 createStore 和 reducer 創(chuàng)建了一個(gè) store,然后 store 的 getState 方法和 dispatch 方法又分別被直接和間接地賦值給 middlewareAPI 變量,middlewareAPI 就是對(duì)比圖中紅色箭頭所指向的函數(shù)的入?yún)?store。
var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI));
然后讓每個(gè) middleware 帶著 middlewareAPI 這個(gè)參數(shù)分別執(zhí)行一遍,即執(zhí)行紅色箭頭指向的函數(shù)。執(zhí)行完后,獲得 chain 數(shù)組,[f1, f2, ... , fx, ...,fn],它保存的對(duì)象是圖中綠色箭頭指向的匿名函數(shù),因?yàn)殚]包,每個(gè)匿名函數(shù)都可以訪(fǎng)問(wèn)相同的 store,即 middlewareAPI。
備注: middlewareAPI 中的 dispatch 為什么要用匿名函數(shù)包裹呢?
我們用 applyMiddleware 是為了改造 dispatch 的,所以 applyMiddleware 執(zhí)行完后,dispatch 是變化了的,而 middlewareAPI 是 applyMiddleware 執(zhí)行中分發(fā)到各個(gè) middleware,所以必須用匿名函數(shù)包裹 dispatch, 這樣只要 dispatch 更新了, middlewareAPI 中的 dispatch 應(yīng)用也會(huì)發(fā)生變化。
Step. 3 組合串聯(lián) middlewares
dispatch = compose(...chain)(store.dispatch);
這一層只有一行代碼,但卻是 applyMiddleware 精華所在。compose 是函數(shù)式編程中的組合,compose 將 chain 中的所有匿名函數(shù),[f1, f2, ... , fx, ..., fn],組裝成一個(gè)新的函數(shù),即新的 dispatch,當(dāng)新 dispatch 執(zhí)行時(shí),[f1, f2, ... , fx, ..., fn],從左到右依次執(zhí)行( 所以順序很重要)。Redux 中 compose 的實(shí)現(xiàn)是下面這樣的,當(dāng)然實(shí)現(xiàn)方式不唯一。
function compose(...funcs) { return arg => funcs.reduceRight((composed, f) => f(composed), arg); }
compose(...chain) 返回的是一個(gè)匿名函數(shù),函數(shù)里的 funcs 就是 chain 數(shù)組,當(dāng)調(diào)用 reduceRight 時(shí),依次從 funcs 數(shù)組的右端取一個(gè)函數(shù) fx 拿來(lái)執(zhí)行,fx 的參數(shù) composed 就是前一次 fx+1 執(zhí)行的結(jié)果,而第一次執(zhí)行的fn(n代表chain的長(zhǎng)度)的參數(shù) arg 就是 store.dispatch。所以當(dāng) compose 執(zhí)行完后,我們得到的 dispatch 是這樣的,假設(shè) n = 3。
dispatch = f1(f2(f3(store.dispatch))))
這個(gè)時(shí)候調(diào)用新 dispatch,每個(gè) middleware 的代碼不就依次執(zhí)行了嘛。
Step. 4 在 middleware 中調(diào)用 dispatch 會(huì)發(fā)生什么
經(jīng)過(guò) compose,所有的 middleware 算是串聯(lián)起來(lái)了,可是還有一個(gè)問(wèn)題,我們有必要挖一挖。在 step 2 時(shí),提到過(guò)每個(gè) middleware 都可以訪(fǎng)問(wèn) store,即 middlewareAPI 這個(gè)變量,所以就可以拿到 store 的 dispatch 方法,那么在 middleware 中調(diào)用 store.dispatch() 會(huì)發(fā)生什么,和調(diào)用 next() 有區(qū)別嗎?比如下圖:
在 step 2 的時(shí)候我們解釋過(guò),通過(guò)匿名函數(shù)的方式,middleware 中 拿到的 dispatch 和最終 compose 結(jié)束后的新 dispatch 是保持一致的,所以在middleware 中調(diào)用 store.dispatch() 和在其他任何地方調(diào)用效果是一樣的,而在 middleware 中調(diào)用 next(),效果是進(jìn)入下一個(gè) middleware。下面這張圖說(shuō)明一切。
正常情況下,如圖左,當(dāng)我們 dispatch 一個(gè) action 時(shí),middleware 通過(guò) next(action) 一層一層處理和傳遞 action 直到 redux 原生的 dispatch。如果某個(gè) middleware 使用 store.dispatch(action) 來(lái)分發(fā) action,就發(fā)生了右圖的情況,相當(dāng)于從外層重新來(lái)一遍,假如這個(gè) middleware 一直簡(jiǎn)單粗暴地調(diào)用 store.dispatch(action),就會(huì)形成無(wú)限循環(huán)了。那么 store.dispatch(action) 的勇武之地在哪里?正確的使用姿勢(shì)應(yīng)該是怎么樣的?
舉個(gè)例子,需要發(fā)送一個(gè)異步請(qǐng)求到服務(wù)器獲取數(shù)據(jù),成功后彈出一個(gè)自定義的 Message。這里我門(mén)用到了 redux-thunk 這個(gè)作者寫(xiě)的 middleware。
const thunk = store => next => action => typeof action === "function" ? action(store.dispatch, store.getState) : next(action)
redux-thunk 做的事情就是判斷 action 類(lèi)型是否是函數(shù),若是,則執(zhí)行 action,若不是,則繼續(xù)傳遞 action 到下個(gè) middleware。
針對(duì)上面的需求,我們?cè)O(shè)計(jì)了下面的 action:
const getThenShow = (dispatch, getState) => { const url = "http://xxx.json"; fetch(url) .then(response => { dispatch({ type: "SHOW_MESSAGE_FOR_ME", message: response.json(), }); }, e => { dispatch({ type: "FETCH_DATA_FAIL", message: e, }); }); };
這個(gè)時(shí)候只要在業(yè)務(wù)代碼里面調(diào)用 store.dispatch(getThenShow),redux-thunk 就會(huì)攔截并執(zhí)行 getThenShow 這個(gè) action,getThenShow 會(huì)先請(qǐng)求數(shù)據(jù),如果成功,dispatch 一個(gè)顯示 Message 的 action,否則 dispatch 一個(gè)請(qǐng)求失敗的 action。這里的 dispatch 就是通過(guò) redux-thunk middleware 傳遞進(jìn)來(lái)的。
在 middleware 中使用 dispatch 的場(chǎng)景一般是:
接受到一個(gè)定向 action,這個(gè) action 并不希望到達(dá)原生的 dsipatch,存在的目的是為了觸發(fā)其他新的 action,往往用在異步請(qǐng)求的需求里。
applyMiddleware 機(jī)制的核心在于組合 compose,將不同的 middlewares 一層一層包裹到原生的 dispatch 之上,而為了方便進(jìn)行 compose,需對(duì) middleware 的設(shè)計(jì)采用柯里化 curry 的方式,達(dá)到動(dòng)態(tài)產(chǎn)生 next 方法以及保持 store 的一致性。由于在 middleware 中,可以像在外部一樣輕松訪(fǎng)問(wèn)到 store, 因此可以利用當(dāng)前 store 的 state 來(lái)進(jìn)行條件判斷,用 dispatch 方法攔截老的 action 或發(fā)送新的 action。
參考Understanding Redux Middleware
Redux Middleware: Behind the Scene
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78741.html
摘要:通過(guò)創(chuàng)建將所有的異步操作邏輯收集在一個(gè)地方集中處理,可以用來(lái)代替中間件。 redux-saga框架使用詳解及Demo教程 前面我們講解過(guò)redux框架和dva框架的基本使用,因?yàn)閐va框架中effects模塊設(shè)計(jì)到了redux-saga中的知識(shí)點(diǎn),可能有的同學(xué)們會(huì)用dva框架,但是對(duì)redux-saga又不是很熟悉,今天我們就來(lái)簡(jiǎn)單的講解下saga框架的主要API和如何配合redux框...
摘要:當(dāng)你應(yīng)用了中間件,在觸發(fā)一個(gè)操作的時(shí)候,操作就會(huì)經(jīng)過(guò)先經(jīng)過(guò)中間件,最終再形成。以其中兩個(gè)中間件為例,說(shuō)明下,一個(gè)觸發(fā)一個(gè)動(dòng)作的時(shí)候,代碼的執(zhí)行邏輯。 為了解析中間件,先看一下幾個(gè)中間件是什么樣子,怎么用,運(yùn)行起來(lái)的原理是什么? 1、中間件是什么樣子的 1.2 thunk中間件 function createThunkMiddleware(extraArgument) { retu...
摘要:每個(gè)接受的和函數(shù)作為命名參數(shù),并返回一個(gè)函數(shù)。調(diào)用鏈中最后一個(gè)會(huì)接受真實(shí)的的方法作為參數(shù),并借此結(jié)束調(diào)用鏈。 簡(jiǎn)介: 手寫(xiě)實(shí)現(xiàn)redux基礎(chǔ)api createStore( )和store相關(guān)方法 api回顧: createStore(reducer, [preloadedState], enhancer) 創(chuàng)建一個(gè) Redux store 來(lái)以存放應(yīng)用中所有的 state reduc...
摘要:假設(shè)等于,其中,,是三個(gè)中間件,等于,那么可以簡(jiǎn)化為。最終返回中的方法以及經(jīng)過(guò)中間件包裝處理過(guò)的方法。以此類(lèi)推,第二個(gè)返回的就是第一個(gè)中間件的形參。根據(jù)這個(gè)的討論,在中間件頂層調(diào)用了,結(jié)果導(dǎo)致無(wú)法執(zhí)行后面的中間件。 redux 主要包含 5 個(gè)方法,分別是: createStore combineReducers bindActionCreators applyMiddleware ...
閱讀 2398·2021-09-22 16:01
閱讀 3164·2021-09-22 15:41
閱讀 1182·2021-08-30 09:48
閱讀 497·2019-08-30 15:52
閱讀 3335·2019-08-30 13:57
閱讀 1720·2019-08-30 13:55
閱讀 3671·2019-08-30 11:25
閱讀 767·2019-08-29 17:25