Vuex 原理解析
# Vuex 原理解析
Vuex 是专门为 Vuejs 应用程序设计的状态管理工具。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
# Vuex 的构成
由上图,我们可以看出Vuex有以下几个部分构成:
1)state
state是存储的单一状态,是存储的基本数据。
2)Getters
getters
是 store
的计算属性,对 state
的加工,是派生出来的数据。就像 computed
计算属性一样,getter返回的值会根据它的依赖被缓存起来,且只有当它的依赖值发生改变才会被重新计算。
3)Mutations
mutations
提交更改数据,使用 store.commit
方法更改 state
存储的状态。(mutations
同步函数)
4)Actions
actions
像一个装饰器,提交 mutation
,而不是直接变更状态。(actions
可以包含任何异步操作)
5)Module
Module
是 store
分割的模块,每个模块拥有自己的 state、getters、mutations、actions
。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
6)辅助函数
Vuex 提供了 mapState、MapGetters、MapActions、mapMutations
等辅助函数给开发在 vm
中处理 store
。
2、Vuex的使用
import Vuex from 'vuex';
Vue.use(Vuex); // 1. vue的插件机制,安装vuex
let store = new Vuex.Store({ // 2.实例化store,调用install方法
state,
getters,
modules,
mutations,
actions,
plugins
});
new Vue({ // 3.注入store, 挂载vue实例
store,
render: h=>h(app)
}).$mount('#app');
2
3
4
5
6
7
8
9
10
11
12
13
14
# Vuex 的设计思想
Vuex 的设计思想,借鉴了Flux、Redux,将数据存放到全局的 store,再将 store 挂载到每个 vue 实例组件中,利用Vue.js 的细粒度数据响应机制来进行高效的状态更新。
看了 Vuex 设计思想,心里难免会有这样的疑问:
- vuex 的 store 是如何挂载注入到组件中呢?
- vuex 的 state 和 getters 是如何映射到各个组件实例中响应式更新状态呢?
# Vuex 的原理解析
我们来看下 vuex 的源码,分析看看上面2个疑惑的问题:
疑问1:vuex 的 store 是如何挂载注入到组件中呢?
1、在 vue 项目中先安装 vuex,核心代码如下:
import Vuex from 'vuex';
Vue.use(vuex);// vue的插件机制
2
2、利用 vue 的插件机制,使用Vue.use(vuex)时,会调用 vuex 的 install 方法,装载 vuex,install 方法的代码如下:
export function install (_Vue) {
if (Vue && _Vue === Vue) {
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
2
3
4
5
6
7
8
9
10
11
12
3、applyMixin
方法使用vue混入机制,vue 的生命周期 beforeCreate
钩子函数前混入vuexInit
方法,核心代码如下:
Vue.mixin({ beforeCreate: vuexInit });
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
2
3
4
5
6
7
8
9
10
11
12
13
分析源码,我们知道了 vuex 是利用 vue 的 mixin 混入机制,在 beforeCreate
钩子前混入 vuexInit
方法,vuexInit
方法实现了store
注入 vue
组件实例,并注册了vuex store
的引用属性 $store
。store
注入过程如下图所示:
疑问2:vuex 的 state 和 getters 是如何映射到各个组件实例中响应式更新状态呢?
store
实现的源码在 src/store.js
1、我们在源码中找到 resetStoreVM
核心方法:
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// 设置 getters 属性
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
// 遍历 wrappedGetters 属性
forEachValue(wrappedGetters, (fn, key) => {
// 给 computed 对象添加属性
computed[key] = partial(fn, store)
// 重写 get 方法
// store.getters.xx 其实是访问了store._vm[xx],其中添加 computed 属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
const silent = Vue.config.silent
Vue.config.silent = true
// 创建Vue实例来保存state,同时让state变成响应式
// store._vm._data.$$state = store.state
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// 只能通过commit方式更改状态
if (store.strict) {
enableStrictMode(store)
}
}
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
从上面源码,我们可以看出 Vuex 的 state
状态是响应式,是借助 vue 的 data
是响应式,将 state
存入 vue
实例组件的 data
中;Vuex 的 getters
则是借助 vue 的计算属性 computed
实现数据实时监听。
3 模块树、状态数以及 getters、actions、mutations 的整合
Store
实例上拥有一个包含所有模块形成的树形的数据结构。并且所有的子模块的 getters、actions、mutations、state
都放统一在 store
实例上。
以如下的配置对象创建 store
export default new Vuex.Store({
state: {
number: 11,
},
getters: {
myAge(state) {
return state.number + 18;
},
},
mutations: {
syncAdd(state, number) {
state.number += number;
},
syncDecline(state, number) {
state.number -= number;
},
},
actions: {
// 异步
asyncDecline({ commit }, number) {
setTimeout(() => {
commit("syncDecline", number);
}, 1000);
},
},
modules: {
a: {
modules: {
c: {
state: { c: 1 },
getters: {
computedC(state, b, c) {
return state.c + 100;
},
},
mutations: {
syncAdd(state, number) {
},
},
actions: {
asyncDeclineC(context, number) {
},
},
},
},
},
b: {
state: {
b: 10,
},
},
},
});
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
生成后的 store
拥有 getters、actions、mutations、state、_module
属性。
_module
属性就是包含了所有子模块的树形结构。
{
root:{
_rawModule,// 模块的属性
_children, // 该模块的子模块
state, // 该模块的状态
}
}
2
3
4
5
6
7
getters、actions、mutations
都是数组其中放入了所有的在组件的对应值。
getters :[/* 所有的模块的getters集合 */]
actions :[/* 所有的模块的actions集合 */]
mutations :[/* 所有的模块的mutations集合 */]
2
3
_module
是用来干什么的呢?
是用来把子模块的 getters、actions、mutations、state
放在根 store
上,基于该 _module
的数据结构递归操作达到目的。使用如下的 installModule
的方法来实现,代码如下:
class Store {
constructor(options = {}) {
this._subscribes = [];
this.s = this.getNewVueInstance(options.state);
this.getters = {};
this.mutations = {}; // 所有同步 操作方法
this.actions = {};
this._modules = new ModuleCollection(options); // 把数据格式化成想要的树形结构
// 递归分类
installModule(this, this.state, [], this._modules.root);
options.plugins.forEach((plugin) => plugin(this));
}
// 其他内容省略...
}
function installModule(store, rootState, path, rootModule) {
if (path.length > 0) {
let parent = path.slice(0, -1).reduce((root, current) => {
if (!root[current]) root[current] = {};
return root[current];
}, rootState);
// 不会导致数据更新使用 Vue.set
// parent[path[path.length - 1]] = rootModule.state;
Vue.set(parent, [path[path.length - 1]], rootModule.state);
} else {
}
let getters = rootModule._rawModule.getters;
let mutations = rootModule._rawModule.mutations;
let actions = rootModule._rawModule.actions;
// 整合子模块的 getters 到根模块
if (getters) {
forEach(getters, (getterName, fn) => {
Object.defineProperty(store.getters, getterName, {
get() {
return fn(rootModule._rawModule.state); // 传入该模块的 state
},
});
});
}
// 整合子模块的 mutations 到根模块
if (mutations) {
forEach(mutations, (mutationName, fn) => {
let mutations = store.mutations[mutationName] || [];
mutations.push((...args) => {
fn(rootModule.state, ...args);
store._subscribes.forEach((
fn // 订阅方法执行
) => fn({ type: mutationName, payload: args }, rootState));
});
store.mutations[mutationName] = mutations;
});
}
// 整合子模块的 actions 到根模块
if (actions) {
forEach(actions, (actionName, fn) => {
let actions = store.actions[actionName] || [];
actions.push((...args) => {
fn(store, ...args);
});
store.actions[actionName] = actions;
});
}
forEach(rootModule._children, (moduleName, module) => {
//递归调用
installModule(store, rootState, path.concat(moduleName), module);
});
}
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
小结
Vuex 是通过全局注入 store
对象,来实现组件间的状态共享。在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用 vuex
比较合适。假如只是多个组件间传递数据,使用 vuex
未免有点大材小用,其实只用使用组件间常用的通信方法即可。