浅析Redux 的 store enhancer

作者: 艾特老干部 | 来源:发表于2017-12-31 13:47 被阅读1100次

    相信大家都知道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

    相关文章

      网友评论

        本文标题:浅析Redux 的 store enhancer

        本文链接:https://www.haomeiwen.com/subject/blzogxtx.html