Vuex——Vue单页应用状态管理架构

Vue.js提供了很好的数据响应机制,让我们能够轻松实现数据变化到视图的更新映射。Vuex是在Flux的设计思想基础上产生的,官网对其解释为状态管理,个人觉得好理解一些就是数据管理。

在做Vue开发,组件之间的数据传递大致可以分为两种,父子关系明确和相互独立的。如果有明确的父子关系,那么通过Vue的事件系统就可以实现。但是对于相互对立的组件,Vue没有可行的解决方案,之前的解决方案是在根组件创建一个事件监听,通过根组件进行分发,或者通过第三方的工具库(postal.js)。有了Vuex我们现在有了更加优雅的解决方式,将这些需要到处传递的数据交给Vuex,保证所有组件都可以监视到变化从而做出响应。

Vuex流程

vuex_flow

Store

Vuex的核心部分,字面意思“仓库”,确确实实也做了存储的功能,我们的应用状态都存储在这里,有如下特点:

  1. 数据是响应式的。
  2. 通过分发突变事件改变store中的值。

store对象包括两个变量,statemutations。state用来存储状态,vuex不允许直接修改state,需要通过mutatiion事件触发。

Action

改变store中状态 ,通过分发mutations的函数,在store里面通过mutations只是对事件进行了注册,真正触发需要在action中通过dispatch来触发。

Getters

从store中获取状态 getter是一个函数,只接受一个state参数,用于返回状态。

明确以上三个概念就可以将Vuex引入我们的项目进行开发了,vuex的使用还是很简单的,下面是一个简单例子:

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
app: '',
path: ''
}
const mutations = {
UPDATEAPP (state, app) {
state.app = app;
},
UPDATEPATH (state, path) {
state.path = path;
}
}
export default new Vuex.Store({
state,
mutations
})

getter.js

1
2
3
4
5
6
7
8
9
//getters.js
export const globalApp = function (state) {
return state.app
}
export const globalPath = function (state) {
return state.path
}

actions.js

1
2
3
4
5
6
7
8
9
//actions.js
export const updateApp = function ({ dispatch }, app) {
dispatch('UPDATEAPP', app)
}
export const updatePath = function ({ dispatch }, path) {
dispatch('UPDATEPATH', path)
}

app入口根组件

1
2
3
4
5
6
7
//index.js
import store from 'core/router/store/store';
import Vue from 'vue'
export default {
store,
}

子组件

1
2
3
4
5
6
7
8
9
10
//app.js
import * as actions from 'core/router/store/actions';
import * as getters from 'core/router/store/getters';
export default {
vuex: {
getters,
actions
}
}

下面从Vuex的源码进行分析,简单梳理一下它的实现原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//index.js
class Store {
constructor ({
state = {},
mutations = {},
modules = {},
middlewares = [],
strict = false
} = {}) {
...
this._vm = new Vue({
data: {
state
}
})
...
}
get state () {
return this._vm.state
}
//静止对state直接赋值
set state (v) {
throw new Error('[vuex] Vuex root state is read only.')
}
//dispatch函数,触发mutation函数
dispatch (type, ...payload) {
...
mutation(state, ...payload)
...
}
}
//导出一个Store类和install函数
export default {
Store,
install
}

上面是我筛选之后认为比较关键的一些代码,Vuex导出一个对象包括一个Store类和install函数,结合调用vuex的方法,可以看到install是Vue加载插件时调用的,Store生成了我们的store对象。在Store的构造函数可以看到,最关键的就是它在内部创建了一个独立的vm,并将state作为其data,这个就是响应式的关键一步,这样就创建了一个独立于其他任何一个组件的vm,实现“全局效果”,那之后都就差一步,就是让所有组件都可以访问到这个vm,并且感知到state的变化。我们继续往下看,看install又做了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
export default function (Vue) {
//在vue实例化阶段调用
if (version >= 2) {
Vue.mixin(usesInit ? { init: vuexInit } : { beforeCreate: vuexInit })
} else {
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
function vuexInit () {
const options = this.$options
const { store, vuex } = options
//为每个组件添加$store对象,并且指向刚才提到的store对象
if (store) {
this.$store = store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
...
const { state, actions } = vuex
let { getters } = vuex
//对实例的getter进行watch
for (const key in getters) {
defineVuexGetter(this, key, getters[key])
}
...
...
//为实例添加action方法
for (const key in actions) {
options.methods[key] = makeBoundAction(this.$store, actions[key], key)
}
...
}
function setter () {
throw new Error('vuex getter properties are read-only.')
}
function defineVuexGetter (vm, key, getter) {
Object.defineProperty(vm, key, {
enumerable: true,
configurable: true,
get: makeComputedGetter(vm.$store, getter),
set: setter
})
}
function makeComputedGetter (store, getter) {
const id = store._getterCacheId
if (getter[id]) {
return getter[id]
}
//watch store创建的vm的state的变化,实现响应式
const vm = store._vm
...
const watcher = new Watcher(
vm,
vm => getter(vm.state),
null,
{ lazy: true }
)
const computedGetter = () => {
...
return watcher.value
}
getter[id] = computedGetter
return computedGetter
}
function makeBoundAction (store, action, key) {
return function vuexBoundAction (...args) {
return action.call(this, store, ...args)
}
}
}

从上面的代码可以看到Vuex改变了Vue实例化过程,做了两件事。第一,为每个组件的getters进行watch,从而对state的变化能够响应;第二,为每个组件扩展了actions方法,让每个组件可以直接调用,通过dispatch修改state的值。

总结一下,Vuex的原理就是通过内部的一个vm实例,充分利用Vue的watch方法,让每个组件都能对state的变化做出响应,并且通过$stored对象,实现对state的修改。到此Vuex核心功能就介绍完了,文章主要希望结合源码能理清实现原理,使用介绍比较简单,跟多内容请移步到Vuex官方文档