# Vuex
# 概念
专为 Vue.js 应用程序开发的状态管理模式
集中式存储管理应用的左右组件的状态
响应式
# Vue.use(Vuex)
- 安装 Vue.js 插件,如果插件是一个对象。必须提供 install 方法,如果插件是一个函数,它会被当做install方法。
install 方法调用时,会将 Vue 当做参数传入,该方法需要在调用 new Vue() 之前被调用
当 install 方法被同一个插件多次调用,插件只会被安装一次。
# 问题思考
- 使用Vuex只需执行 Vue.use(Vuex),并在Vue的配置中传入一个store对象的示例,store是如何实现注入的?
- state内部是如何实现支持模块配置和模块嵌套的?
- 在执行dispatch触发action(commit同理)的时候,只需传入(type, payload),action执行函数中第一个参数store从哪里获取的?
- 如何区分state是外部直接修改,还是通过mutation方法修改的?
- 调试时的“时空穿梭”功能是如何实现的?
# 目录介绍
| -- src
| -- | -- module # 提供 module 对象 与 module 对象树的创建功能
| -- | -- | -- | module-collection.js
| -- | -- | -- | module.js
| -- | -- plugins # 提供开发辅助插件,如 时空穿梭, state 修改记录功能等
| -- | -- | -- | devtool.js
| -- | -- | -- | logger.js
| -- helpers.js # 提供 actions mutations getters 的查找api
| -- index.js # 入口文件
| -- minxin.js # 提供 store 在 Vue 实例上装载注入
| -- store.js # 提供 store 各 module 构建安装
| -- util.js # 工具方法
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 入口
- vuex/src/index.js
import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
export default {
Store,
install,
version: '__VERSION__',
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 装载注入
- 使用例子
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
// 安装插件 调用 install 方法 或直接 install()
Vue.use(Vuex)
const store = new Vuex.Store({})
export default store
// main.js
new Vue({
el: '#app',
store
// ...
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- install 方法 vuex/src/store.js
export function install (_Vue) {
// Vue 已经存在并且相等,说明已经Vuex.use过
if (Vue && _Vue === Vue) {
// 非生产环境报错,vuex已经安装,代码继续执行
if (process.env.NODE_ENV !== 'production') {
console.error(
'[vuex] already installed. Vue.use(Vuex) should be called only once.'
)
}
return
}
Vue = _Vue
applyMixin(Vue)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
- applyMixin(Vue)
将初始化Vue根组件时传入store设置到
this.$store
上,子组件从其父组件上获取$store
属性,
层层嵌套进行设置,在任意组件上都能找到 store 对象
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// 合并选项后 beforeCreate 是数组里函数的形式 [func, func]
// 最后调用循环遍历这个数组,调用这些函数,这是一种函数与函数合并的解决方案。
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
/**
* Vuex init hook, injected into each instances init hooks list.
*/
function vuexInit () {
console.log('vuexInit')
const options = this.$options
// store 注入
// 使得每个Vue实例下 都有 $store 这个对象(Store 实例,包含一系列方法和属性),且是同一个对象。
// 先是判断 options.store 也就是 这个
/*
const store = new Vuex.Store();
new Vue({
store,
})
*/
// store injection
if (options.store) {
console.log('options.store')
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
console.log('options.parent.$store')
this.$store = options.parent.$store
}
}
}
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
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
# 数据初始化
- vuex/src/store.js
export class Store {
constructor (options = {}) {
// 环境判断
// 如果是 cdn script 引入vuex插件,则自动安装vuex插件,不需要用Vue.use(Vuex)来安装
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
// 不是生产环境
// 断言函数
/**
* 条件 断言 不满足直接抛出错误
export function assert (condition, msg) {
if (!condition) throw new Error(`[vuex] ${msg}`)
}
*/
if (process.env.NODE_ENV !== 'production') {
// 可能有读者会问:为啥不用console.assert,console.assert函数报错不会阻止后续代码执行
// 必须使用Vue.use(Vuex) 创建 store 实例
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
// 当前环境不支持Promise,报错:vuex需要Promise polyfill
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
// Store 函数必须使用new操作符调用
assert(this instanceof Store, `store must be called with the new operator.`)
}
// store internal state
// store 实例对象 内部的 state
this._committing = false
// 用来存放处理后的用户自定义的actoins
/**
* 提一下 Object.create(null) 和 {} 的区别。前者没有原型链,后者有。
即 Object.create(null).__proto__ 是 undefined
({}).__proto__ 是 Object.prototype
*/
this._actions = Object.create(null)
// 用来存放 actions 订阅
this._actionSubscribers = []
// 用来存放处理后的用户自定义的mutations
this._mutations = Object.create(null)
// 用来存放处理后的用户自定义的 getters
this._wrappedGetters = Object.create(null)
// 模块收集器,构造模块树形结构
this._modules = new ModuleCollection(options)
// 用于存储模块命名空间的关系
this._modulesNamespaceMap = Object.create(null)
// 订阅
this._subscribers = []
// 用于使用 $watch 观测 getters
this._watcherVM = new Vue()
// 用来存放生成的本地 getters 的缓存
this._makeLocalGettersCache = Object.create(null)
// bind commit and dispatch to self
// 给自己 绑定 commit 和 dispatch
const store = this
const { dispatch, commit } = this
// 为何要这样绑定 ?
// 说明调用commit和dispach 的 this 不一定是 store 实例
// 这是确保这两个函数里的this是store实例
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
// ...
// 初始化 根模块
// 并且也递归的注册所有子模块
// 并且收集所有模块的 getters 放在 this._wrappedGetters 里面
installModule(this, state, [], this._modules.root)
// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
// 初始化 store._vm 响应式的
// 并且注册 _wrappedGetters 作为 computed 的属性
resetStoreVM(this, state)
// ...
}
}
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
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
# module树构造
- vuex/src/module/module-collection.js
主要将传入的 options 对象 整个构造为一个 module 对象,并循环调用
this.register()
为其中的 modules 属性进行模块注册,
使其成为 module 对象,最后 options 对象被构造成一个完整的组件树。
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
// 注册根模块 参数 rawRootModule 也就是 Vuex.Store 的 options 参数
// 未加工过的模块(用户自定义的),根模块
this.register([], rawRootModule, false)
}
/**
* 注册模块
* @param {Array} path 路径
* @param {Object} rawModule 原始未加工的模块
* @param {Boolean} runtime runtime 默认是 true
*/
register (path, rawModule, runtime = true) {
// 非生产环境 断言判断用户自定义的模块是否符合要求
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule)
}
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
// 递归注册子模块
if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
}
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
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
# dispatch 和 commit 设置
- 使用示例
因为支持两种形式调用 所以 dispatch() commit() 首先会调用
unifyObjectStyle()
格式化参数
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
store.commit('increment', {
amount: 10
})
store.commit({
type: 'increment',
amount: 10
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// bind commit and dispatch to self
// 给自己 绑定 commit 和 dispatch
const store = this
const { dispatch, commit } = this
// 为何要这样绑定 ?
// 说明调用commit和dispach 的 this 不一定是 store 实例
// 这是确保这两个函数里的this是store实例
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
commit (_type, _payload, _options) {
// check object-style commit
// 统一成对象风格
/**
* 支持多种方式
* 最后返回 { type, payload, options }
* store.commit('increment', {
* count: 10
* })
* // 对象提交方式
* store.commit({
* type: 'increment',
* count: 10
* })
*/
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
// 取出处理后的用户定义 mutation
const entry = this._mutations[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 专用修改state方法,其他修改state方法均是非法修改
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 订阅者函数遍历执行,传入当前的mutation对象和当前的state
this._subscribers.forEach(sub => sub(mutation, this.state))
if (
process.env.NODE_ENV !== 'production' &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
// dispatch
dispatch (_type, _payload) {
// check object-style dispatch
// 获取到type和payload参数
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
// 声明 action 变量 等于 type和payload参数
const action = { type, payload }
// 入口,也就是 _actions 集合
const entry = this._actions[type]
// 如果不存在
if (!entry) {
// 非生产环境报错,匹配不到 action 类型
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
// 不往下执行
return
}
try {
this._actionSubscribers
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
return res
})
}
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# state 修改方法
- 所有触发 mutation 进行修改 state 的操作都要经过 _withCommit(),统一管理 state
// 专用修改state方法,其他修改state方法均是非法修改
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 内部方法 _withCommit _committing 变量 主要是给严格模式下
// enableStrictMode 函数 监控是否是通过这个函数修改,不是则报错。
_withCommit (fn) {
// 存储committing 变量
const committing = this._committing
// committing 为 true
this._committing = true
// 执行参数 fn 函数
fn()
// committing 为 true
this._committing = committing
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# module 安装
- 初始化组件树根组件,注册所有子组件,并将所有的 getters 存储到 this._wrappedGetters 中
function installModule (store, rootState, path, module, hot) {
// 是根模块
const isRoot = !path.length
// 命名空间 字符串
/**
* getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
*/
const namespace = store._modules.getNamespace(path)
// register in namespace map
/**
*
* 注册在命名空间的map对象中。
* 模块命名控件为 true 执行以下代码
* 主要用于在 helpers 辅助函数,根据命名空间获取模块
* function getModuleByNamespace (store, helper, namespace) {
// _modulesNamespaceMap 这个变量在 class Store 中
const module = store._modulesNamespaceMap[namespace]
if (process.env.NODE_ENV !== 'production' && !module) {
console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`)
}
return module
}
*/
if (module.namespaced) {
// 模块命名空间map对象中已经有了,开发环境报错提示重复
if (store._modulesNamespaceMap[namespace] && process.env.NODE_ENV !== 'production') {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
// module 赋值给 _modulesNamespaceMap[namespace]
store._modulesNamespaceMap[namespace] = module
}
// set state
// 不是根模块且不是热重载
if (!isRoot && !hot) {
// 获取父级的state
const parentState = getNestedState(rootState, path.slice(0, -1))
// 模块名称
// 比如 cart
const moduleName = path[path.length - 1]
// state 注册
store._withCommit(() => {
if (process.env.NODE_ENV !== 'production') {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
/**
* 最后得到的是类似这样的结构且是响应式的数据 比如
*
Store实例:{
// 省略若干属性和方法
// 这里的state是只读属性 可搜索 get state 查看
state: {
cart: {
checkoutStatus: null,
items: []
}
}
}
*
*/
// 将 state 设置到父级state对象 moduleName 属性中
Vue.set(parentState, moduleName, module.state)
})
}
// module.context 这个赋值主要是给 helpers 中 mapState、mapGetters、mapMutations、mapActions四个辅助函数使用的。
// 生成本地的dispatch、commit、getters和state
// 主要作用就是抹平差异化,不需要用户再传模块参数
const local = module.context = makeLocalContext(store, namespace, path)
/**
* 循环遍历注册 mutation
* module.forEachMutation 函数 ===== forEachAction 和 forEachGetter 也类似
* 定义在 class Module 里
* _rawModule.mutations 是用户定义的未加工的mutations
* forEachMutation (fn) {
* if (this._rawModule.mutations) {
* forEachValue(this._rawModule.mutations, fn)
* }
* }
*/
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
// 循环遍历注册 action
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
// 循环遍历注册 getter
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
/**
* 注册子模块
* forEachChild (fn) {
forEachValue(this._children, fn)
}
*/
// 递归调用自身
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# store._vm组件设置
- Vuex 是一个名为 store 的 vm 组件,所有的配置 actions mutations state getters 都是组件的属性
function resetStoreVM (store, state, hot) {
// 存储一份老的Vue实例对象 _vm
const oldVm = store._vm
// bind store public getters
// 绑定 store.getter
store.getters = {}
// reset local getters cache
// 重置 本地getters的缓存
store._makeLocalGettersCache = Object.create(null)
// 注册时收集的处理后的用户自定义的 wrappedGetters
const wrappedGetters = store._wrappedGetters
// 声明 计算属性 computed 对象
const computed = {}
// 遍历 wrappedGetters 赋值到 computed 上
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
// direct inline function use will lead to closure preserving oldVm.
// using partial to return function with only arguments preserved in closure environment.
/**
* partial 函数
* 执行函数 返回一个新函数
export function partial (fn, arg) {
return function () {
return fn(arg)
}
}
*/
computed[key] = partial(fn, store)
// getter 赋值 keys
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
// 可以枚举
enumerable: true // for local getters
})
})
// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
// 使用一个 Vue 实例对象存储 state 树
// 阻止警告 用户添加的一些全局mixins
// 声明变量 silent 存储用户设置的静默模式配置
const silent = Vue.config.silent
// 静默模式开启
Vue.config.silent = true
store._vm = new Vue({
data: {
$$state: state
},
computed
})
// 把存储的静默模式配置赋值回来
Vue.config.silent = silent
// enable strict mode for new vm
// 开启严格模式 执行这句
// 用$watch 观测 state,只能使用 mutation 修改 也就是 _withCommit 函数
if (store.strict) {
enableStrictMode(store)
}
// 如果存在老的 _vm 实例
if (oldVm) {
// 热加载为 true
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
// 设置 oldVm._data.$$state = null
store._withCommit(() => {
oldVm._data.$$state = null
})
}
// 实例销毁
Vue.nextTick(() => oldVm.$destroy())
}
}
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
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