淺析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的增強(qiáng)器,顧名思義,就是增強(qiáng)store的功能。一個store enhancer,實際上就是一個高階函數(shù),它的參數(shù)是創(chuàng)建store的函數(shù)(store creator),返回值是一個可以創(chuàng)建功能更加強(qiá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的默認(rèn)行為,在每次發(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)的功能,當(dāng)然也可以使用store enhancer 來實現(xiàn)了。從applyMiddleware(...middlewares)最終的返回結(jié)構(gòu){...store, dispatch}還可以推測出,applyMiddleware(...middlewares)這個store enhancer 主要用來修改store的dispatch方法,這也確實是middleware的作用:增強(qiáng)store的dispatch功能。middleware實際上是在applyMiddleware(...middlewares) 這個store enhancer之上的一層抽象,applyMiddleware(...middlewares) 傳遞給每一個middleware參數(shù){getState, dispatch},middleware對dispatch方法進(jìn)行加強(qiáng)。

當(dāng)同時使用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)層,每當(dāng)發(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限制為只可以增強(qiáng)store dispatch的功能,但這只是它自身的規(guī)范限制,對于其他store enhancer,你可以增強(qiáng)store中包含的任意方法的功能,如dispatch、subscribe、getState、replaceReducer等。畢竟,store只是一個包含一些函數(shù)的普通JavaScript對象,可以很容易的復(fù)制其中的方法,并增加新的功能。

我們再來看一個例子,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的變化,當(dāng)store中的state發(fā)生變化時,將state持久化到localStorage。

3. 破壞性enhancer

因為store enhancer中,我們可以任意復(fù)制、改變store中的方法,所以在自定義store enhancer時,有可能會因為破壞了Redux的正常工作流,導(dǎo)致整個應(yīng)用無法正常工作。下面就是一個破壞性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ā)送出去,導(dǎo)致action無法正常發(fā)送,整個應(yīng)用自然也就無法工作。所以,自定義store enhancer時,一定要注意,不要破壞了Redux的原有工作流。


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

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容