redux applyMiddleware 原理剖析

用法

為了對(duì)中間件有一個(gè)整體的認(rèn)識(shí)屁置,先從用法開(kāi)始分析扶欣。調(diào)用中間件的代碼如下:

源碼 createStore.js#39

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
}

enhancer 是中間件,且第二個(gè)參數(shù)為 Function 且沒(méi)有第三個(gè)參數(shù)時(shí)蛀骇,可以轉(zhuǎn)移到第二個(gè)參數(shù)评汰,那么就有兩種方式設(shè)置中間件:

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

再看 源碼 中間件的傳參:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => { 
    var store = createStore(reducer, preloadedState, enhancer)
    ... 
}

就是為了得到 store,并通過(guò) createStore 創(chuàng)建祠肥,上述兩種方法因?yàn)樵?createStore 函數(shù)內(nèi)部傳入了自身函數(shù)才得以實(shí)現(xiàn) :

export default function createStore(reducer, preloadedState, enhancer) {
    ...
    if (typeof enhancer !== 'undefined') {
      return enhancer(createStore)(reducer, preloadedState)
    }
    ...
}

上述代碼可以看出武氓,創(chuàng)建 store 的過(guò)程完全交給中間件了,因此開(kāi)啟了中間件第三種使用方式:

const store = applyMiddleware(...)(createStore)

applyMiddleware 源碼解析

大家對(duì)剖析 applyMiddleware 源碼都非常感興趣,因?yàn)樗鼘?shí)現(xiàn)精簡(jiǎn)县恕,但含義甚廣东羹,再重溫其源碼

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

假設(shè)大家都已了解 ES6 7 語(yǔ)法,懂得 compose 函數(shù)的含義弱睦,并且看過(guò)一些源碼剖析了百姓,我們才能把重點(diǎn)放在核心原理上:為什么中間件函數(shù)有三個(gè)傳參 store => next => action 渊额,第二個(gè)參數(shù) next 為什么擁有神奇的作用况木?

store

代碼前幾行創(chuàng)建了 store (如果第三個(gè)參數(shù)是中間件,就會(huì)出現(xiàn)中間件 store 包中間件 store 的情況旬迹,但效果是完全 打平 的), middlewareAPI 這個(gè)變量火惊,其實(shí)就是精簡(jiǎn)的 store, 因?yàn)樗峁┝?getState 獲取數(shù)據(jù),dispatch 派發(fā)動(dòng)作奔垦。

下一行屹耐,middlewares.map 將這個(gè) store 作為參數(shù)執(zhí)行了一遍中間件,所以中間件第一級(jí)參數(shù) store 就是這么來(lái)的椿猎。

next

下一步我們得到了 chain, 倒推來(lái)看惶岭,其中每個(gè)中間件只有 next => action 兩級(jí)參數(shù)了。我們假設(shè)只有一個(gè)中間件 fn 犯眠,因此 compose 的效果是:

dispatch = fn(store.dispatch)

那么 next 參數(shù)也知道了按灶,就是 store.dispatch 這個(gè)原始的 dispatch.

action

代碼的最后,返回了 dispatch 筐咧,我們一般會(huì)這么用:

store.dispatch(action)

等價(jià)于

fn(store.dispatch)(action)

第三個(gè)參數(shù)也來(lái)了鸯旁,它就是用戶(hù)自己傳的 action.

單一中間件的場(chǎng)景

我們展開(kāi)代碼來(lái)查看一個(gè)中間件的運(yùn)行情況:

fn(middlewareAPI)(store.dispatch)(action)

對(duì)應(yīng) fn 的代碼可能是:

export default store => next => action => {
    console.log('beforeState', store.getState())
    next(action)
    console.log('nextState', store.getState())
}

當(dāng)我們執(zhí)行了 next(action) 后,相當(dāng)于調(diào)用了原始 store dispatch 方法量蕊,并將 action 傳入其中铺罢,可想而知,下一行輸出的 state 已經(jīng)是更新后的了残炮。

但是 next 僅僅是 store.dispatch, 為什么叫做 next 我們現(xiàn)在還看不出來(lái)韭赘。

詳見(jiàn) dispatch 后立刻修改 state:

function dispatch(action) {
    ...
    currentState = currentReducer(currentState, action)
    ...
}

其中還有一段更新監(jiān)聽(tīng)數(shù)組對(duì)象,以達(dá)到 dispatch 過(guò)程不受干擾(快照效果) 作為課后作業(yè)大家獨(dú)立研究:主要思考這段代碼的意圖:https://github.com/reactjs/redux/blob/master/src/createStore.js#L63

多中間件的場(chǎng)景

我們假設(shè)有三個(gè)中間件 fn1 fn2 fn3, 從源碼的這兩句入手:

chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)

第一行代碼势就,我們得到了只剩 next => action 參數(shù)的 chain, 暫且叫做:

cfn1 cfn2 cfn3, 并且有如下對(duì)應(yīng)關(guān)系

cfnx = fnx(middlewareAPI)

第二行代碼展開(kāi)后是這樣的:

dispatch = cfn1(cfn2(cfn3(store.dispatch)))

可以看到最后傳入的中間件 fn3 最先執(zhí)行辞居。

為了便于后面理解,我先把上面代碼的含義寫(xiě)出來(lái):通過(guò)傳入原始的 store.dispatch, 希望通過(guò)層層中間件的調(diào)用蛋勺,最后產(chǎn)生一個(gè)新的 dispatch. 那么實(shí)際上瓦灶,中間件所組成的 dispatch, 從函數(shù)角度看,就是被執(zhí)行過(guò)一次的 cfn1 cfn2 cfn3 函數(shù)抱完。

我們就算不理解新 dispatch 的含義贼陶,也可以從代碼角度理解:只要執(zhí)行了新的 dispatch , 中間件函數(shù) cfnx 系列就要被執(zhí)行一次,所以 cfnx 的函數(shù)本身就是中間件的 dispatch

對(duì)應(yīng) cfn3 的代碼可能是:

export default next => action => {
    next(action)
}

這就是這個(gè)中間件的 dispatch.

那么執(zhí)行了 cfn3 后碉怔,也就是 dispatch 了之后烘贴,其內(nèi)部可能沒(méi)有返回值,我們叫做 ncfn3撮胧,大概如下:

export default action => {}

其函數(shù)自身就是返回值 返回給了 cfn2 作為第一個(gè)參數(shù)桨踪,替代了 cnf3 參數(shù) store.dispatch 的位置。

我們?cè)傧胂耄?code>store.dispatch 的返回值是什么芹啥?不就是 action => {} 這樣的函數(shù)嗎锻离?這樣,一個(gè)中間件的 dispatch 傳遞完成了墓怀。我們理解了多中間件 compose 后可以為什么可以組成一個(gè)新的 dispatch 了(其實(shí)單一中間件也一樣汽纠,但因?yàn)椴襟E只有一步,讓人會(huì)想到直接觸發(fā) store.dispatch 上傀履,多中間件提煉了這個(gè)行為虱朵,上升到組合為新的 dispatch)。

再解釋 next 的含義

為什么我們?cè)谥虚g件中執(zhí)行 next(action) 钓账,下一步就能拿到修改過(guò)的 store ?

對(duì)于 cfn3 來(lái)說(shuō)碴犬, next 就是 store.dispatch 。我們先不考慮它為什么是 next , 但執(zhí)行它了就會(huì)直接執(zhí)行 store.dispatch 梆暮,后面立馬拿到修改后的數(shù)據(jù)不奇怪吧服协。

對(duì)于 cfn2 來(lái)說(shuō),next 就是 cfn3 執(zhí)行后的返回值(執(zhí)行后也還是個(gè)函數(shù)惕蹄,內(nèi)層并沒(méi)有執(zhí)行)蚯涮,我們分為兩種情況:

  1. cfn3 沒(méi)有執(zhí)行 next(action),那 cfn1 cfn2 都沒(méi)法執(zhí)行 store.dispatch卖陵,因?yàn)樵嫉?dispatch 沒(méi)有傳遞下去遭顶,你會(huì)發(fā)現(xiàn) dispatch 函數(shù)被中間件搞失效了(所以中間件還可以搗亂)。為了防止中間件瞎搗亂泪蔫,在中間件正常的情況請(qǐng)執(zhí)行 next(action).

這就是 redux-thunk 的核心思想棒旗,如果 action 是個(gè) function ,就故意執(zhí)行 action , 而不執(zhí)行 next(action) , 等于讓 store.dispatch 失效了撩荣!但其目的是明確的铣揉,因?yàn)闀?huì)把 dispatch 返回給用戶(hù),讓用戶(hù)自己調(diào)用餐曹,正常使用是不會(huì)把流程停下來(lái)的逛拱。

  1. cfn3 執(zhí)行了 next(action), 那 cfn2 什么時(shí)候執(zhí)行 next(action) 台猴,cfn3 就什么時(shí)候執(zhí)行 next(action) => store.dispatch(action) , 所以這一步的 next 效果與 cfn3 相同朽合,繼續(xù)傳遞下去也同理俱两。我看了下 redux-logger 的文檔,果然央求用戶(hù)把自己放在最后一個(gè)曹步,其原因是害怕最右邊的中間件『搗亂』宪彩,不執(zhí)行 next(action) , 那 logger 再執(zhí)行 next(action) 也無(wú)法真正觸發(fā) dispatch .

我在考慮這樣會(huì)不會(huì)有很大的局限性,但后來(lái)發(fā)現(xiàn)讲婚,只要中間件常規(guī)情況執(zhí)行了 next(action) 就能保證原始的 dispatch 可以被繼續(xù)分發(fā)下去尿孔。只要每個(gè)中間件都按照這個(gè)套路來(lái), next(action) 的效果就與 yield 類(lèi)似筹麸。

所以 next 并不是完全意義上的洋蔥模型活合,只能說(shuō)符合規(guī)范(默認(rèn)都執(zhí)行了 next(action))的中間件才符合洋蔥模型。

koa 的洋蔥模型可是有技術(shù)保證的竹捉,generator 可不會(huì)受到代碼的影響芜辕,而 redux 中間件的洋蔥模型尚骄,會(huì)因?yàn)槟骋粚硬粓?zhí)行 next(action) 而中斷块差,而且從右開(kāi)始直接切斷。

為什么在中間件直接 store.dispatch(action) 倔丈,傳遞就會(huì)中斷憨闰?

理解了上面說(shuō)的話(huà),就很簡(jiǎn)單了需五,并不是 store.dispatch(action) 中斷了原始 dispatch 的傳遞鹉动,而是你執(zhí)行完以后不調(diào)用 next 函數(shù)中斷了傳遞。

總結(jié)

還是要畫(huà)個(gè)圖總結(jié)一下宏邮,在不想看文字的時(shí)候:

0cbb4b06-c769-11e6-97ad-7998ac6bab19.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泽示,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蜜氨,更是在濱河造成了極大的恐慌械筛,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件飒炎,死亡現(xiàn)場(chǎng)離奇詭異埋哟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)郎汪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)赤赊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人煞赢,你說(shuō)我怎么就攤上這事抛计。” “怎么了照筑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵吹截,是天一觀(guān)的道長(zhǎng)录豺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)饭弓,這世上最難降的妖魔是什么双饥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮弟断,結(jié)果婚禮上咏花,老公的妹妹穿的比我還像新娘。我一直安慰自己阀趴,他們只是感情好昏翰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著刘急,像睡著了一般棚菊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叔汁,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天统求,我揣著相機(jī)與錄音,去河邊找鬼据块。 笑死码邻,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的另假。 我是一名探鬼主播像屋,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼边篮!你這毒婦竟也來(lái)了己莺?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戈轿,失蹤者是張志新(化名)和其女友劉穎凌受,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體凶杖,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胁艰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了智蝠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腾么。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖杈湾,靈堂內(nèi)的尸體忽然破棺而出解虱,到底是詐尸還是另有隱情,我是刑警寧澤漆撞,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布殴泰,位于F島的核電站于宙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏悍汛。R本人自食惡果不足惜捞魁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望离咐。 院中可真熱鬧谱俭,春花似錦、人聲如沸宵蛀。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)术陶。三九已至凑懂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梧宫,已是汗流浹背接谨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祟敛,地道東北人疤坝。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓兆解,卻偏偏與公主長(zhǎng)得像馆铁,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锅睛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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

  • 一埠巨、什么情況需要redux? 1现拒、用戶(hù)的使用方式復(fù)雜 2辣垒、不同身份的用戶(hù)有不同的使用方式(比如普通用戶(hù)和管...
    初晨的筆記閱讀 2,028評(píng)論 0 11
  • 前言 本文 有配套視頻,可以酌情觀(guān)看印蔬。 文中內(nèi)容因各人理解不同勋桶,可能會(huì)有所偏差,歡迎朋友們聯(lián)系我討論侥猬。 文中所有內(nèi)...
    珍此良辰閱讀 11,906評(píng)論 23 111
  • 看到這篇文章build an image gallery using redux saga例驹,覺(jué)得寫(xiě)的不錯(cuò),長(zhǎng)短也適...
    smartphp閱讀 6,156評(píng)論 1 29
  • “中間件”這個(gè)詞聽(tīng)起來(lái)很恐怖退唠,但它實(shí)際一點(diǎn)都不難鹃锈。想更好的了解中間件的方法就是看一下那些已經(jīng)實(shí)現(xiàn)了的中間件是怎么工...
    Jmingzi_閱讀 1,688評(píng)論 1 7
  • http://gaearon.github.io/redux/index.html ,文檔在 http://rac...
    jacobbubu閱讀 79,959評(píng)論 35 198