redux作为一个纯状态管理库,不单单能和react配合使用,还可以和vue、Angular、Ember、jQuery 甚至纯JavaScript结合使用。可能大家已经注意到,在redux和react配合使用时,我们还引入了一个”react-redux”库,”react-redux”就是专门为了将redux应用在react中而开发,所以你会看到react-redux使用Context、HOC这些react技术。既然redux应用可以这么广,那么本文我们就将redux应用在vue中试试看。
我们仍然以”待办事项”作为例子。首先要有一个基础的UI项目,使用vue把它构建起来,大家请看这里vue-base。具体的搭建我就不在这里细说了。

1、整合方式

首先,我们来看,如何在vue中应用redux?这一点我们可以参考vuex这个专门为vue设计的状态管理库。vuex是以插件的方式引入vue中。vue中提供的插件增加功能的方式,允许使用者添加全局功能。

vue中定义一个插件,要求插件对象有一个install方法,vue在添加插件的时候会将Vue自身作为参数传入install方法,然后将用户自定义的参数以第二个参数options传入。

const MyPlugin = {}
MyPlugin.install = function (Vue, options) {
    // ...
}

定义好插件之后,只需要使用Vue.use()方法便可将插件引入到vue中。

import Vue from 'vue'
Vue.use(MyPlugin, {...options})

Vue的插件功能具体能做的事情如下:

  1. 添加全局方法或属性
  2. 添加全局资源:指令/过滤器/过渡等
  3. 通过全局 mixin 方法添加一些组件选项
  4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。

本文我们会使用到第3点。

MyPlugin.install = function (Vue, options) {
  // 注入组件
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })
}

2、整合细节

假如我们现在已经有了一个由redux创建的store对象。我们要考虑的几个问题是:

  1. vue组件如何获取到redux的state状态(即store.getState()的返回值)。
  2. 当state状态变化时,如何让vue组件中使用的状态也跟着变化。
  3. vue组件中如何dispatch一个Action。

问题1

我们一个问题一个问题来处理。首先第1个,我们可以直接通过将store.getState()方法的返回值mixin到Vue组件中即可。

我们的插件命名为vueRedux的话,那么它看起来时这样的。

const vueRedux = {}
vueRedux.install = function (Vue, options) {
  const { store } = options 
  // 注入组件
  Vue.mixin({
    beforeCreate: function () {
      this.$store = {}
      this.$store.state = store.getState()
    }
  })
}
export default vueRedux

上面这段代码,在Vue的beforeCreate钩子函数中,我们为组建创建了一个this.$store的空对象,然后将store.getState()的返回值保存在this.$store的state属性中。定义this.$store,主要是想将store相关的属性和方法都放在一个对象下面,方便管理。

问题2

接着我们来看第2个问题“当state状态变化时,如何让vue组件中使用的状态也跟着变化”。首先,我们都知道如果redux的state发生变化,我们可以通过store.subscribe()方法来订阅感知state的变化。那获取到新的state后怎样更新到各个组件到$store.state中去呢?

这时,我们需要知道vue本身对数据的控制就是响应式的,比如说定义了一个a属性,b属性依赖于a,那么当a变化时b也会跟着变化。

new Vue({
    data () {
        return {
            a: 'hello world'
        }
    },
    computed: {
        b () {
            return a + ' xxx'
        }
    }
})

上面这段代码,假如我修改了a的值,computed中定义的b的值也会跟着变化。基于这个理解,我们就可以来解决第2个问题了。我们可以将store.getState()的返回值定义为一个vue实例的state属性,每当检测到store的state发生变化,就更新这个vue实例的state属性。然后我们就把这个vue实例mixin到Vue组件中,不就让state变成响应式的了么?代码如下:

const vueRedux = {}
vueRedux.install = (Vue, { store }) => {
    // 将redux的state放置到一个vue的实例中,
    // 每当更新vue实例的state,就能利用vue的特性来触发组件中依赖了state的属性进行更新
    store._vm = new Vue({
        data: {
          state: store.getState()
        },
        created () {
            this.init()
        },
        methods: {
            init () {
                // redux状态变化
                store.subscribe(() => {
                    this.state = store.getState()
                })
            }
        }
    })
    // 使用mixin为每一个组件都添加$store属性,以获取store的state和方法
    Vue.mixin({
        beforeCreate: function () {
            this.$store = store._vm
        }
    })
}

export default vueRedux

引入这个插件后的Vue组件中,就可以通过this.$store.state来获取应用的状态了。

// 示例代码
export default {
 computed: {
     visibilityFilter () {
         const state = this.$store.state
         return state.visibilityFilter
     }
 }
}

问题3

第3个问题就比较简单,我们只需要为Vue组件mixin进一个dispatch方法即可。这个dispatch()方法就调用store.dispatch()方法来派发action。

// 使用mixin为每一个组件都添加$store属性,以获取store的state和方法
    Vue.mixin({
        beforeCreate: function () {
            this.$store = store._vm
            this.$store.dispatch = (action) => {
                store.dispatch(action)
            }
        }
    })

3、插件代码

经过上一节的分析,vueRedux插件的代码如下:


const vueRedux = {}
vueRedux.install = (Vue, { store }) => {
    // 将redux的state放置到一个vue的实例中,
    // 每当更新vue实例的state,就能利用vue的特性来触发组件中依赖了state的属性进行更新
    store._vm = new Vue({
        data: {
          state: store.getState()
        },
        created () {
            this.init()
        },
        methods: {
            init () {
                // redux状态变化
                store.subscribe(() => {
                    this.state = store.getState()
                })
            }
        }
    })
    // 使用mixin为每一个组件都添加$store属性,以获取store的state和方法
    Vue.mixin({
        beforeCreate: function () {
            this.$store = store._vm
            this.$store.dispatch = (action) => {
                store.dispatch(action)
            }
        }
    })
}

export default vueRedux

4、最后

要把这个插件应用起来,我们需要使用Vue.use()方法引入插件。

import Vue from 'vue'
// 假如已经有了一个创建redux store并导出store的store.js文件
import store from './store'
Vue.use(vueRedux, { store })

本文主要逻辑在于如何实现vueRedux这个插件,文章中的代码并不完整。具体的示例代码大家可以撮这里vue-03

此外,对于vue和redux整合,github上也有对应的库vueRedux。这个库和本文的思想很相近,大家要将redux应用在vue的话,可以考虑这个库。不过,当然和vue配合的最好状态管理库是vuex。