redux_middleware詳解

為什么dispatch需要middleware

middle.png

上圖表達(dá)的是 redux 中一個簡單的同步數(shù)據(jù)流動的場景劫扒,點擊 button 后,在回調(diào)中 dispatch 一個 action狸膏,reducer 收到 action 后沟饥,更新 state 并通知 view 重新渲染。單向數(shù)據(jù)流湾戳,看著沒什么問題贤旷。但是,如果需要打印每一個 action 信息用來調(diào)試砾脑,就得去改 dispatch 或者 reducer 代碼幼驶,使其具有打印日志的功能;又比如點擊 button 后韧衣,需要先去服務(wù)器請求數(shù)據(jù)盅藻,只有等拿到數(shù)據(jù)后,才能重新渲染 view畅铭,此時我們又希望 dispatch 或者 reducer 擁有異步請求的功能氏淑;再比如需要異步請求完數(shù)據(jù)后,打印一條日志硕噩,再請求數(shù)據(jù)假残,再打印日志,再渲染...

面對多種多樣的業(yè)務(wù)需求榴徐,單純的修改 dispatchreducer 的代碼顯然不具有普世性,我們需要的是可以組合的匀归,自由插拔的插件機制坑资,這一點 redux 借鑒了 koa 里中間件的思想,koa 是用于構(gòu)建 web 應(yīng)用的 NodeJS 框架穆端。另外 reducer 更關(guān)心的是數(shù)據(jù)的轉(zhuǎn)化邏輯袱贮,所以 reduxmiddleware 是為了增強 dispatch 而出現(xiàn)的。

middle2.png

上面這張圖展示了應(yīng)用 middleware 后 redux 處理事件的邏輯体啰,每一個 middleware 處理一個相對獨立的業(yè)務(wù)需求攒巍,通過串聯(lián)不同的 middleware嗽仪,實現(xiàn)變化多樣的的功能。那么問題來了:

* middleware 怎么寫柒莉?
* redux 是如何讓 middlewares 串聯(lián)并跑起來的闻坚?

理解middleware機制

redux 提供了 applyMiddleware 這個 api 來加載 middleware,為了方便理解兢孝,下圖將兩者的源碼放在一起進行分析窿凤。

middle3.png

圖下邊是 logger,打印 action 的 middleware跨蟹,圖上邊則是 applyMiddleware 的源碼雳殊,applyMiddleware 代碼雖然只有二十多行,卻非常精煉窗轩,接下來我們就分四步來深入解析這張圖夯秃。

  • 第一步:函數(shù)式編程思想設(shè)計 middleware

    middleware 的設(shè)計有點特殊,是一個層層包裹的匿名函數(shù)痢艺,這其實是函數(shù)式編程中的柯里化 curry仓洼,一種使用匿名單參數(shù)函數(shù)來實現(xiàn)多參數(shù)函數(shù)的方法。applyMiddleware 會對 logger 這個 middleware 進行層層調(diào)用腹备,動態(tài)地對 store 和 next 參數(shù)賦值衬潦。

柯里化的 middleware 結(jié)構(gòu)好處在于:

  1. 易串聯(lián),柯里化函數(shù)具有延遲執(zhí)行的特性植酥,通過不斷柯里化形成的 middleware 可以累積參數(shù)镀岛,配合組合( compose,函數(shù)式編程的概念友驮,Step. 2 中會介紹)的方式漂羊,很容易形成 pipeline 來處理數(shù)據(jù)流。

2.共享store卸留,在 applyMiddleware 執(zhí)行過程中走越,store 還是舊的,但是因為閉包的存在耻瑟,applyMiddleware 完成后旨指,所有的 middlewares 內(nèi)部拿到的 store 是最新且相同的。

另外喳整,我們可以發(fā)現(xiàn) applyMiddleware 的結(jié)構(gòu)也是一個多層柯里化的函數(shù)谆构,借助 compose , applyMiddleware 可以用來和其他插件一起加強 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);
  • 第二步 給 middleware 分發(fā) store

創(chuàng)建一個普通的 store 通過如下方式:

let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, null);

上面代碼執(zhí)行完后框都,applyMiddleware 函數(shù)陸續(xù)獲得了三個參數(shù)搬素,第一個是 middlewares 數(shù)組,[mid1, mid2, mid3, ...],第二個 next 是 Redux 原生的 createStore熬尺,最后一個是 reducer摸屠。我們從對比圖中可以看到,applyMiddleware 利用 createStore 和 reducer 創(chuàng)建了一個 store粱哼,然后 store 的 getState 方法和 dispatch 方法又分別被直接和間接地賦值給 middlewareAPI 變量季二,middlewareAPI 就是對比圖中紅色箭頭所指向的函數(shù)的入?yún)?store。

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    };
    chain = middlewares.map(middleware => middleware(middlewareAPI));

map 方法讓每個 middleware 帶著 middlewareAPI 這個參數(shù)分別執(zhí)行一遍皂吮,即執(zhí)行紅色箭頭指向的函數(shù)戒傻。執(zhí)行完后,獲得 chain 數(shù)組蜂筹,[f1, f2, ... , fx, ...,fn]需纳,它保存的對象是圖中綠色箭頭指向的匿名函數(shù),因為閉包艺挪,每個匿名函數(shù)都可以訪問相同的 store不翩,即 middlewareAPI。

  • 第三步 組合串聯(lián) middlewares

    dispatch = compose(...chain)(store.dispatch);

    這一層只有一行代碼麻裳,但卻是 applyMiddleware 精華所在口蝠。compose 是函數(shù)式編程中的組合,compose 將 chain 中的所有匿名函數(shù),[f1, f2, ... , fx, ..., fn],組裝成一個新的函數(shù)呆抑,即新的 dispatch,當(dāng)新 dispatch 執(zhí)行時眉反,[f1, f2, ... , fx, ..., fn],從左到右依次執(zhí)行( 所以順序很重要)穆役。Redux 中 compose 的實現(xiàn)是下面這樣的寸五,當(dāng)然實現(xiàn)方式不唯一。

         function compose(...funcs) {
          return arg => funcs.reduceRight((composed, f) => f(composed), arg);
      }
    

    compose(...chain) 返回的是一個匿名函數(shù)耿币,函數(shù)里的 funcs 就是 chain 數(shù)組梳杏,當(dāng)調(diào)用 reduceRight 時,依次從 funcs 數(shù)組的右端取一個函數(shù) fx 拿來執(zhí)行淹接,fx 的參數(shù) composed 就是前一次 fx+1 執(zhí)行的結(jié)果十性,而第一次執(zhí)行的fn(n代表chain的長度)的參數(shù) arg 就是 store.dispatch。所以當(dāng) compose 執(zhí)行完后塑悼,我們得到的 dispatch 是這樣的劲适,假設(shè) n = 3。

dispatch = f1(f2(f3(store.dispatch))))

這個時候調(diào)用新 dispatch拢肆,每個 middleware 的代碼不就依次執(zhí)行了嘛.

  • 第四步 在 middleware 中調(diào)用 dispatch 會發(fā)生什么

經(jīng)過 compose减响,所有的 middleware 算是串聯(lián)起來了,可是還有一個問題郭怪,我們有必要挖一挖支示。在 step 2 時,提到過每個 middleware 都可以訪問 store鄙才,即 middlewareAPI 這個變量颂鸿,所以就可以拿到 store 的 dispatch 方法,那么在 middleware 中調(diào)用 store.dispatch()會發(fā)生什么攒庵,和調(diào)用 next() 有區(qū)別嗎嘴纺?

在 step 2 的時候我們解釋過,通過匿名函數(shù)的方式浓冒,middleware 中 拿到的 dispatch 和最終 compose 結(jié)束后的新 dispatch 是保持一致的栽渴,所以在middleware 中調(diào)用 store.dispatch() 和在其他任何地方調(diào)用效果是一樣的,而在 middleware 中調(diào)用 next()稳懒,效果是進入下一個 middleware闲擦。

正常情況下當(dāng)我們 dispatch 一個 action 時,middleware 通過 next(action) 一層一層處理和傳遞 action 直到 redux 原生的 dispatch场梆。如果某個 middleware 使用 store.dispatch(action) 來分發(fā) action相當(dāng)于從外層重新來一遍墅冷,假如這個 middleware 一直簡單粗暴地調(diào)用 store.dispatch(action),就會形成無限循環(huán)了或油。那么 store.dispatch(action) 的勇武之地在哪里寞忿?正確的使用姿勢應(yīng)該是怎么樣的?舉個例子顶岸,需要發(fā)送一個異步請求到服務(wù)器獲取數(shù)據(jù)腔彰,成功后彈出一個自定義的 Message。這里我門用到了 redux-thunk 這個作者寫的 middleware蜕琴。

const thunk = store => next => action =>
  typeof action === 'function' ?
    action(store.dispatch, store.getState) :
    next(action)

redux-thunk 做的事情就是判斷 action 類型是否是函數(shù)萍桌,若是,則執(zhí)行 action凌简,若不是上炎,則繼續(xù)傳遞 action 到下個 middleware。

針對上面的需求雏搂,我們設(shè)計了下面的 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,
    });
  });
};

這個時候只要在業(yè)務(wù)代碼里面調(diào)用 store.dispatch(getThenShow)藕施,redux-thunk 就會攔截并執(zhí)行 getThenShow 這個 action,getThenShow 會先請求數(shù)據(jù)凸郑,如果成功裳食,dispatch 一個顯示 Message 的 action,否則 dispatch 一個請求失敗的 action芙沥。這里的 dispatch 就是通過 redux-thunk middleware 傳遞進來的诲祸。

在 middleware 中使用 dispatch 的場景一般是:
接受到一個定向 action浊吏,這個 action 并不希望到達(dá)原生的 dsipatch,存在的目的是為了觸發(fā)其他新的 action救氯,往往用在異步請求的需求里找田。

總結(jié)

applyMiddleware 機制的核心在于組合 compose,將不同的 middlewares 一層一層包裹到原生的 dispatch 之上着憨,而為了方便進行 compose墩衙,需對 middleware 的設(shè)計采用柯里化 curry 的方式,達(dá)到動態(tài)產(chǎn)生 next 方法以及保持 store 的一致性甲抖。由于在 middleware 中漆改,可以像在外部一樣輕松訪問到 store, 因此可以利用當(dāng)前 store 的 state 來進行條件判斷,用 dispatch 方法攔截老的 action 或發(fā)送新的 action准谚。

github
最后挫剑,希望這篇博客對大家有所幫助(如果是,請盡情star哦柱衔,??)暮顺,歡迎提出您的寶貴建議~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秀存,隨后出現(xiàn)的幾起案子捶码,更是在濱河造成了極大的恐慌,老刑警劉巖或链,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惫恼,死亡現(xiàn)場離奇詭異,居然都是意外死亡澳盐,警方通過查閱死者的電腦和手機祈纯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叼耙,“玉大人腕窥,你說我怎么就攤上這事∩竿瘢” “怎么了簇爆?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長爽撒。 經(jīng)常有香客問我入蛆,道長,這世上最難降的妖魔是什么硕勿? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任哨毁,我火速辦了婚禮,結(jié)果婚禮上源武,老公的妹妹穿的比我還像新娘扼褪。我一直安慰自己想幻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布话浇。 她就那樣靜靜地躺著举畸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凳枝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天跋核,我揣著相機與錄音岖瑰,去河邊找鬼。 笑死砂代,一個胖子當(dāng)著我的面吹牛蹋订,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刻伊,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼露戒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捶箱?” 一聲冷哼從身側(cè)響起智什,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丁屎,沒想到半個月后荠锭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡晨川,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年证九,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片共虑。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡愧怜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妈拌,到底是詐尸還是另有隱情拥坛,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布尘分,位于F島的核電站渴逻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏音诫。R本人自食惡果不足惜惨奕,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望竭钝。 院中可真熱鬧梨撞,春花似錦雹洗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至港粱,卻和暖如春螃成,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背查坪。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工寸宏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人偿曙。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓氮凝,卻偏偏與公主長得像,于是被迫代替她去往敵國和親望忆。 傳聞我的和親對象是個殘疾皇子罩阵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • http://gaearon.github.io/redux/index.html ,文檔在 http://rac...
    jacobbubu閱讀 79,964評論 35 198
  • 前言 本文 有配套視頻启摄,可以酌情觀看稿壁。 文中內(nèi)容因各人理解不同,可能會有所偏差歉备,歡迎朋友們聯(lián)系我討論常摧。 文中所有內(nèi)...
    珍此良辰閱讀 11,906評論 23 111
  • 學(xué)習(xí)必備要點: 首先弄明白,Redux在使用React開發(fā)應(yīng)用時威创,起到什么作用——狀態(tài)集中管理 弄清楚Redux是...
    賀賀v5閱讀 8,902評論 10 58
  • 一落午、什么情況需要redux? 1肚豺、用戶的使用方式復(fù)雜 2溃斋、不同身份的用戶有不同的使用方式(比如普通用戶和管...
    初晨的筆記閱讀 2,030評論 0 11
  • 做React需要會什么梗劫? react的功能其實很單一,主要負(fù)責(zé)渲染的功能截碴,現(xiàn)有的框架梳侨,比如angular是一個大而...
    蒼都閱讀 14,759評論 1 139