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的插件功能具体能做的事情如下:
- 添加全局方法或属性
- 添加全局资源:指令/过滤器/过渡等
- 通过全局 mixin 方法添加一些组件选项
- 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
本文我们会使用到第3点。
MyPlugin.install = function (Vue, options) {
// 注入组件
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
}
2、整合细节
假如我们现在已经有了一个由redux创建的store对象。我们要考虑的几个问题是:
- vue组件如何获取到redux的state状态(即store.getState()的返回值)。
- 当state状态变化时,如何让vue组件中使用的状态也跟着变化。
- 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。