淺析Redux 的 store enhancer

相信大家都知道Redux的middleware(中間件)的概念,Redux通過middleware可以完成發(fā)送異步action(網(wǎng)絡(luò)請求)叹括、打印action的日志等功能算墨。相對而言,Redux的store enhancer的概念汁雷,很多人并不是很清楚净嘀。

1. 基本概念及使用

Redux通過API createStore創(chuàng)建store,createStore的函數(shù)簽名如下:

createStore(reducer, [preloadedState], [enhancer])

其中侠讯,第3個可選參數(shù)enhancer挖藏,就是指的store enhancer。

store enhancer厢漩,可以翻譯成store的增強器膜眠,顧名思義,就是增強store的功能。一個store enhancer宵膨,實際上就是一個高階函數(shù)架谎,它的參數(shù)是創(chuàng)建store的函數(shù)(store creator),返回值是一個可以創(chuàng)建功能更加強大的store的函數(shù)(enhanced store creator)柄驻,這和React中的高階組件的概念很相似狐树。

store enhancer 函數(shù)的結(jié)構(gòu)一般如下:

function enhancerCreator() {
  return createStore => (...args) => {
    // do something based old store
    // return a new enhanced store
  }
}

注意,enhancerCreator是用于創(chuàng)建store enhancer 的函數(shù)鸿脓,也就是說enhancerCreator的執(zhí)行結(jié)果才是一個store enhancer抑钟。(…args) 參數(shù)代表創(chuàng)建store所需的參數(shù),也就是createStore接收的參數(shù)野哭,實際上就是(reducer, [preloadedState], [enhancer])在塔。

現(xiàn)在,我們來創(chuàng)建一個store enhancer拨黔,用于輸出發(fā)送的action的信息和state的變化:

// autoLogger.js
// store enhancer
export default function autoLogger() {
  return createStore => (reducer, initialState, enhancer) => {
    const store = createStore(reducer, initialState, enhancer)
    function dispatch(action) {
      console.log(`dispatch an action: ${JSON.stringify(action)}`);
      const res = store.dispatch(action);
      const newState = store.getState();
      console.log(`current state: ${JSON.stringify(newState)}`);
      return res;
    }
    return {...store, dispatch}
  }
}

autoLogger() 改變了store dispatch的默認行為蛔溃,在每次發(fā)送action前后,都會輸出日志信息篱蝇。然后在創(chuàng)建store上贺待,使用autoLogger()這個store enhancer:

// configureStore.js
import { createStore, applyMiddleware } from 'redux';
import rootReducer from 'path/to/reducers';
import autLogger from 'path/to/autoLogger';

const store = createStore(
  reducer,
  autoLogger()
);

2. 與middleware (中間件)的關(guān)系

如果你了解redux-logger這個redux middleware零截,是不是感覺autoLogger()的作用和它很相似呢麸塞?難道store enhancer 和 middleware 可以實現(xiàn)相同的功能?確實如此涧衙。實際上哪工,middleware的實現(xiàn)函數(shù)applyMiddleware本身就是一個store enhancer,applyMiddleware源碼示意如下:

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // 省略
    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware的結(jié)構(gòu)和前面提到的store enhancer的結(jié)構(gòu)完全相同弧哎,applyMiddleware(...middlewares)的執(zhí)行結(jié)果就是一個store enhancer雁比。所以,可以用middleware實現(xiàn)的功能撤嫩,當然也可以使用store enhancer 來實現(xiàn)了偎捎。從applyMiddleware(...middlewares)最終的返回結(jié)構(gòu){...store, dispatch}還可以推測出,applyMiddleware(...middlewares)這個store enhancer 主要用來修改store的dispatch方法序攘,這也確實是middleware的作用:增強store的dispatch功能鸭限。middleware實際上是在applyMiddleware(...middlewares) 這個store enhancer之上的一層抽象,applyMiddleware(...middlewares) 傳遞給每一個middleware參數(shù){getState, dispatch}两踏,middleware對dispatch方法進行加強败京。

當同時使用applyMiddleware(...middlewares)和其他store enhancer時,往往可以先使用redux提供的compose函數(shù)梦染,將這些store enhancer組合成一個:

// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import rootReducer from 'path/to/reducers';
import autLogger from 'path/to/autoLogger';

const enhancer = compose(
  applyMiddleware(...middlewares),
  autLogger()
);

const store = createStore(
  reducer赡麦,
  enhancer
);

經(jīng)過compose組合后朴皆,所有的store enhancer會形成一個洋蔥模型,compose中的第一個enhancer處于洋蔥模型的最外層泛粹,最后一個enhancer處于洋蔥模型的最內(nèi)層遂铡,每當發(fā)送一個action,都會經(jīng)過洋蔥模型從外到內(nèi)的每一層enhancer的處理晶姊,如下圖所示扒接。因為一般我們通過middleware處理異步action,為保證其他enhancer接收到的都是普通action们衙,所以需要將applyMiddleware(...middlewares)作為第一個store enhancer 傳給 compose钾怔,讓所有的action先經(jīng)過applyMiddleware(...middlewares)的處理。

圖 1

applyMiddleware(…middlewares)將middleware限制為只可以增強store dispatch的功能蒙挑,但這只是它自身的規(guī)范限制宗侦,對于其他store enhancer,你可以增強store中包含的任意方法的功能忆蚀,如dispatch矾利、subscribe、getState馋袜、replaceReducer等男旗。畢竟,store只是一個包含一些函數(shù)的普通JavaScript對象欣鳖,可以很容易的復制其中的方法察皇,并增加新的功能。

我們再來看一個例子观堂,redux-localstorage让网, 這個store enhancer 用來自動將store中的state持久化到localStorage中呀忧。它的主要代碼如下:

// store enhancer
export default function persistState(paths, config) {
  // 一些功能選項配置

  return next => (reducer, initialState, enhancer) => {
    let persistedState
    let finalInitialState

    try {
      persistedState = deserialize(localStorage.getItem(key))
      finalInitialState = merge(initialState, persistedState)
    } catch (e) {
      console.warn('Failed to retrieve initialize state from localStorage:', e)
    }

    const store = next(reducer, finalInitialState, enhancer)
    const slicerFn = slicer(paths)
    
    // 主要代碼
    store.subscribe(function () {
      const state = store.getState()
      const subset = slicerFn(state)

      try {
        localStorage.setItem(key, serialize(subset))
      } catch (e) {
        console.warn('Unable to persist state to localStorage:', e)
      }
    })

    return store
  }
}

這個enhancer做的事情其實很簡單师痕,只是在創(chuàng)建store后,立即訂閱了store的變化而账,當store中的state發(fā)生變化時胰坟,將state持久化到localStorage。

3. 破壞性enhancer

因為store enhancer中泞辐,我們可以任意復制笔横、改變store中的方法,所以在自定義store enhancer時咐吼,有可能會因為破壞了Redux的正常工作流吹缔,導致整個應用無法正常工作。下面就是一個破壞性enhancer的例子:

export default function breakingEnhancer() {
  return createStore => (reducer, initialState, enhancer) => {
    const store = createStore(reducer, initialState, enhancer)
    function dispatch(action) {
      console.log(`dispatch an action: ${JSON.stringify(action)}`);
      console.log(`current state: ${JSON.stringify(newState)}`);
      return res;
    }
    return {...store, dispatch}
  }
}

這個例子重新創(chuàng)建了一個dispatch方法锯茄,但在新的dispatch方法中厢塘,并沒有調(diào)用老的dispatch方法茶没,將action發(fā)送出去,導致action無法正常發(fā)送晚碾,整個應用自然也就無法工作抓半。所以,自定義store enhancer時格嘁,一定要注意笛求,不要破壞了Redux的原有工作流。


歡迎關(guān)注我的公眾號:老干部的大前端糕簿,領(lǐng)取21本大前端精選書籍探入!

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市冶伞,隨后出現(xiàn)的幾起案子新症,更是在濱河造成了極大的恐慌,老刑警劉巖响禽,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徒爹,死亡現(xiàn)場離奇詭異,居然都是意外死亡芋类,警方通過查閱死者的電腦和手機隆嗅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侯繁,“玉大人胖喳,你說我怎么就攤上這事≈梗” “怎么了丽焊?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咕别。 經(jīng)常有香客問我技健,道長,這世上最難降的妖魔是什么惰拱? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任雌贱,我火速辦了婚禮,結(jié)果婚禮上偿短,老公的妹妹穿的比我還像新娘欣孤。我一直安慰自己,他們只是感情好昔逗,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布降传。 她就那樣靜靜地躺著,像睡著了一般勾怒。 火紅的嫁衣襯著肌膚如雪婆排。 梳的紋絲不亂的頭發(fā)上款票,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音泽论,去河邊找鬼艾少。 笑死,一個胖子當著我的面吹牛翼悴,可吹牛的內(nèi)容都是我干的缚够。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼鹦赎,長吁一口氣:“原來是場噩夢啊……” “哼谍椅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起古话,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤雏吭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后陪踩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體杖们,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年肩狂,在試婚紗的時候發(fā)現(xiàn)自己被綠了摘完。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡傻谁,死狀恐怖孝治,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情审磁,我是刑警寧澤谈飒,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站态蒂,受9級特大地震影響杭措,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吃媒,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一瓤介、第九天 我趴在偏房一處隱蔽的房頂上張望吕喘。 院中可真熱鬧赘那,春花似錦、人聲如沸氯质。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闻察。三九已至拱礁,卻和暖如春琢锋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呢灶。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工吴超, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸯乃。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓鲸阻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缨睡。 傳聞我的和親對象是個殘疾皇子鸟悴,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 前言 本文 有配套視頻,可以酌情觀看奖年。 文中內(nèi)容因各人理解不同细诸,可能會有所偏差,歡迎朋友們聯(lián)系我討論陋守。 文中所有內(nèi)...
    珍此良辰閱讀 11,894評論 23 111
  • 為什么dispatch需要middleware 上圖表達的是 redux 中一個簡單的同步數(shù)據(jù)流動的場景震贵,點擊 b...
    一個胖子的我閱讀 1,977評論 1 9
  • http://gaearon.github.io/redux/index.html ,文檔在 http://rac...
    jacobbubu閱讀 79,900評論 35 198
  • “中間件”這個詞聽起來很恐怖水评,但它實際一點都不難屏歹。想更好的了解中間件的方法就是看一下那些已經(jīng)實現(xiàn)了的中間件是怎么工...
    Jmingzi_閱讀 1,670評論 1 7
  • Redux這個npm包,提供若干API讓我們使用reducer創(chuàng)建store之碗,并能更新store中的數(shù)據(jù)或獲取st...
    不安分的三好份子閱讀 920評論 0 0