深入解读Vuex源码
# 深入解读Vuex源码
Vuex 是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex 提供了一个集中式的数据状态管理存储仓库,允许 Vue 组件对其进行数据的访问和通讯,并且 Vuex 限定了 Vue 对其数据进行操作的规则,保证数据的统一性。
# 功能特性
Vue Component 负责数据的渲染,Vuex 负责数据的状态管理,Vue Component 通过dispatch
函数触发 Vuex 对应action
函数的执行,action
函数内部调用commit
函数触发对应mutation
函数执行,mutation
函数可访问 Vuex 的 state 对象并对其进行修改,响应式的 state 数据在被修改后触发执行 Vue Component 的render
函数的重载,从而把 state 数据更新到渲染视图。
# 目录结构
src
├── module # 模块相关操作
│ ├── module-collection.js # 模块对象树构建
│ └── module.js # 模块对象定义
├── plugins # 相关插件
│ ├── devtool.js # 调试插件
│ └── logger.js # 日志插件
├── helpers.js # 相关辅助函数
├── index.cjs.js # commonjs 入口文件
├── index.js # 默认入口文件
├── index.mjs # esModule 入口文件
├── mixin.js # store 对象注入实现
├── store.js # store 对象定义
└── util.js # 相关工具函数
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
# 应用实例
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
// $store 属性注入
Vue.use(Vuex)
// 创建 store 对象
export default new Vuex.Store({...})
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
store 对象插入
// main.js
import Vue from 'vue'
import App from './App'
improt store from './store'
new Vue({
el: '#root',
store, // 通过 options 传参传入 store 对象
render: h => h(App)
})
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# Vuex 的装载与注入
查看Vue.use(plugin)方法定义,可以发现其内部会调用 plugin 的install方法。
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
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
查看 Vuex 源码的入口文件 index.js,install方法的定义在文件 store.js 中。
// 通过局部变量 Vue,判断是否已装载
let Vue // bind on install
...
export class Store {
constructor (options = {}) {
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
// 如果是浏览器环境上通过 CDN 方式加载 Vue,则自动执行 install 方法
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
...
}
}
export function install (_Vue) {
// 防止 Vuex 重复装载
if (Vue && _Vue === Vue) {
if (__DEV__) {
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
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
// applyMixin 方法定义
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])
if (version >= 2) {
// Vue2 通过 mixin 使用 hook 方式进行 store 对象注入
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
// Vue1 通过重写原型 _init 方法进行 store 对象注入
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 () {
const options = this.$options
// store injection
// store 注入
// 保证在任意组件访问 $store 属性都指向同一个 store 对象
if (options.store) {
// 将 store 对象注入到根组件的 $store 属性上
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
// 将子组件的 $store 属性指向父组件的 $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
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
# 数据初始化与 module 对象树构建
// store internal state
this._committing = false // 表示 commit 状态,用于判断是否是通过 commit 修改 state 属性
this._actions = Object.create(null) // 存储封装后的 actions 集合
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options) // 构建 module 对象树
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
// module-collection.js
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
...
register (path, rawModule, runtime = true) {
if (__DEV__) {
// 校验 module 对象结构
assertRawModule(path, rawModule)
}
// 创建 module 对象,提供内部属性操作方法,如 addChild 等
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
// 根module
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
// 通过递归构建嵌套 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
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
// module-collection.js
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
...
register (path, rawModule, runtime = true) {
if (__DEV__) {
// 校验 module 对象结构
assertRawModule(path, rawModule)
}
// 创建 module 对象,提供内部属性操作方法,如 addChild 等
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {
// 根module
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// register nested modules
// 通过递归构建嵌套 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
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
export default class Module {
constructor (rawModule, runtime) {
this.runtime = runtime
// Store some children item
this._children = Object.create(null)
// Store the origin module object which passed by programmer
this._rawModule = rawModule // 存储当前模块
const rawState = rawModule.state
// Store the origin module's state
// 可以允许 state 属性是一个返回一个对象的函数或对象
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
// module 对象更行方法
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# commit 和 dispatch 函数配置
// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
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)
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
commit (_type, _payload, _options) {
// check object-style commit
// 配置参数校验和处理
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)
const mutation = { type, payload }
const entry = this._mutations[type]
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
// 用于判断是否是通过 commit 修改 state 属性
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
// 如果有订阅函数存在,则逐个执行
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(sub => sub(mutation, this.state))
if (
__DEV__ &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. ` +
'Use the filter functionality in the vue-devtools'
)
}
}
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)
const action = { type, payload }
const entry = this._actions[type]
if (!entry) {
if (__DEV__) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}
try {
this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(sub => sub.before)
.forEach(sub => sub.before(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in before action subscribers: `)
console.error(e)
}
}
// 通过异步 Promise 向 actionSubscribers 传递 action 执行结果并执行
const result = entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
return new Promise((resolve, reject) => {
result.then(res => {
try {
this._actionSubscribers
.filter(sub => sub.after)
.forEach(sub => sub.after(action, this.state))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in after action subscribers: `)
console.error(e)
}
}
resolve(res)
}, error => {
try {
this._actionSubscribers
.filter(sub => sub.error)
.forEach(sub => sub.error(action, this.state, error))
} catch (e) {
if (__DEV__) {
console.warn(`[vuex] error in error action subscribers: `)
console.error(e)
}
}
reject(error)
})
})
}
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
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
# module 安装
// strict mode
this.strict = strict
const state = this._modules.root.state
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 初始化 rootModule
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// register in namespace map
// 注册进模块 namespace map,防止命名冲突
if (module.namespaced) {
if (store._modulesNamespaceMap[namespace] && __DEV__) {
console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
}
store._modulesNamespaceMap[namespace] = module
}
// set state
// 把模块的 state 设置到 state._vm.$data 的 $$state 属性中,其中 state._vm 定义在 resetStoreVM 中
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
if (__DEV__) {
if (moduleName in parentState) {
console.warn(
`[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
)
}
}
Vue.set(parentState, moduleName, module.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
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
# module 上下文环境设置
// module上下文环境生成
const local = module.context = makeLocalContext(store, namespace, path)
1
2
2
# mutations、actions 以及 getters 注册
// 注册一系列 mutations 、actions 以及 getters,并将其 this 绑定到当前 store 对象
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 子 module 安装
// 递归安装子 module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
1
2
3
4
2
3
4
# plugins 注入
// apply plugins
plugins.forEach(plugin => plugin(this))
1
2
2