浅析Redux 的 store enhancer

相信大家都知道Redux的middleware(中间件)的概念,Redux通过middleware可以完成发送异步action(网络请求)、打印action的日志等功能。相对而言,Redux的store enhancer的概念,很多人并不是很清楚。

1. 基本概念及使用

Redux通过API createStore创建store,createStore的函数签名如下:

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

其中,第3个可选参数enhancer,就是指的store enhancer。

store enhancer,可以翻译成store的增强器,顾名思义,就是增强store的功能。一个store enhancer,实际上就是一个高阶函数,它的参数是创建store的函数(store creator),返回值是一个可以创建功能更加强大的store的函数(enhanced store creator),这和React中的高阶组件的概念很相似。

store enhancer 函数的结构一般如下:

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

注意,enhancerCreator是用于创建store enhancer 的函数,也就是说enhancerCreator的执行结果才是一个store enhancer。(…args) 参数代表创建store所需的参数,也就是createStore接收的参数,实际上就是(reducer, [preloadedState], [enhancer])。

现在,我们来创建一个store enhancer,用于输出发送的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的默认行为,在每次发送action前后,都会输出日志信息。然后在创建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 (中间件)的关系

如果你了解redux-logger这个redux middleware,是不是感觉autoLogger()的作用和它很相似呢?难道store enhancer 和 middleware 可以实现相同的功能?确实如此。实际上,middleware的实现函数applyMiddleware本身就是一个store enhancer,applyMiddleware源码示意如下:

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

applyMiddleware的结构和前面提到的store enhancer的结构完全相同,applyMiddleware(...middlewares)的执行结果就是一个store enhancer。所以,可以用middleware实现的功能,当然也可以使用store enhancer 来实现了。从applyMiddleware(...middlewares)最终的返回结构{...store, dispatch}还可以推测出,applyMiddleware(...middlewares)这个store enhancer 主要用来修改store的dispatch方法,这也确实是middleware的作用:增强store的dispatch功能。middleware实际上是在applyMiddleware(...middlewares) 这个store enhancer之上的一层抽象,applyMiddleware(...middlewares) 传递给每一个middleware参数{getState, dispatch},middleware对dispatch方法进行加强。

当同时使用applyMiddleware(...middlewares)和其他store enhancer时,往往可以先使用redux提供的compose函数,将这些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
);

经过compose组合后,所有的store enhancer会形成一个洋葱模型,compose中的第一个enhancer处于洋葱模型的最外层,最后一个enhancer处于洋葱模型的最内层,每当发送一个action,都会经过洋葱模型从外到内的每一层enhancer的处理,如下图所示。因为一般我们通过middleware处理异步action,为保证其他enhancer接收到的都是普通action,所以需要将applyMiddleware(...middlewares)作为第一个store enhancer 传给 compose,让所有的action先经过applyMiddleware(...middlewares)的处理。

图 1

applyMiddleware(…middlewares)将middleware限制为只可以增强store dispatch的功能,但这只是它自身的规范限制,对于其他store enhancer,你可以增强store中包含的任意方法的功能,如dispatch、subscribe、getState、replaceReducer等。毕竟,store只是一个包含一些函数的普通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做的事情其实很简单,只是在创建store后,立即订阅了store的变化,当store中的state发生变化时,将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}
  }
}

这个例子重新创建了一个dispatch方法,但在新的dispatch方法中,并没有调用老的dispatch方法,将action发送出去,导致action无法正常发送,整个应用自然也就无法工作。所以,自定义store enhancer时,一定要注意,不要破坏了Redux的原有工作流。


欢迎关注我的公众号:老干部的大前端,领取21本大前端精选书籍!

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 前言 本文 有配套视频,可以酌情观看。 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我讨论。 文中所有内...
    珍此良辰阅读 11,892评论 23 111
  • 为什么dispatch需要middleware 上图表达的是 redux 中一个简单的同步数据流动的场景,点击 b...
    一个胖子的我阅读 1,977评论 1 9
  • http://gaearon.github.io/redux/index.html ,文档在 http://rac...
    jacobbubu阅读 79,894评论 35 198
  • “中间件”这个词听起来很恐怖,但它实际一点都不难。想更好的了解中间件的方法就是看一下那些已经实现了的中间件是怎么工...
    Jmingzi_阅读 1,667评论 1 7
  • Redux这个npm包,提供若干API让我们使用reducer创建store,并能更新store中的数据或获取st...
    不安分的三好份子阅读 920评论 0 0