深入 Vuex

深入 Vuex

在上一章 Vuex 框架核心流程 当中,我们介绍了 Vuex 的一些常见的 API 以及初始化装载与注入的流程,我们可以再来看一下入口文件,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'

export default {
Store,
install,
version: '__VERSION__',
mapState,
mapMutations,
mapGetters,
mapActions,
createNamespacedHelpers
}

通过观察可以发现,其实 Vuex 真正核心的就是 store 这个东西,而始化装载与注入(install)我们在上一章已经介绍过了,剩下的一些也都是一些辅助方法,所以在本章,我们就深入的来了解一下这个 store 到底是什么东西

通常我们在使用 Vuex 的时候,会实例化 store 类,然后传入一个对象,包括我们定义好的 actionsgettersmutationsstate 等,甚至当我们有多个子模块的时候,我们可以添加一个 modules 对象,store 对象的逻辑比较复杂,下面有一个构造方法的整体逻辑流程

环境判断

1
2
3
4
5
6
7
8
9
10
// store.js  ==>  https://github.com/vuejs/vuex/blob/dev/src/store.js

// 使用断言函数,
// 确保 Vue 的存在,也就是在实例化 Store 之前,必须要保证之前的 install 方法已经执行过
// 另外一点就是需要支持 promise 语法,因为 Vuex 是依赖 promise 的
if (process.env.NODE_ENV !== 'production') {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
assert(this instanceof Store, `Store must be called with the new operator.`)
}

assert 函数是一个简单的断言函数

1
2
3
4
// util.js
export function assert(condition, msg) {
if (!condition) throw new Error(`[vuex] ${msg}`)
}

数据初始化、module 树构造

然后根据 new 构造传入的 options 或默认值,初始化内部数据

1
2
3
4
5
6
7
// 利用解构赋值,拿到 options 里面的 plugins 和 strict
// plugins 表示应用的插件
// strict 表示是否开启严格模式
const {
plugins = [],
strict = false
} = options
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
// 是否在进行提交状态标识,作用是保证对 Vuex 中 state 的修改只能在 mutation 的回调函数中
// 而不能在外部随意修改 state
this._committing = false

// 存储用户定义的所有的 actions
this._actions = Object.create(null)
this._actionSubscribers = []

// mutations
this._mutations = Object.create(null)

// 封装后的 getters 集合对象
this._wrappedGetters = Object.create(null)

// 用于支持 store 分模块传入,存储分析后的 modules
this._modules = new ModuleCollection(options)

// 模块命名空间 map
this._modulesNamespaceMap = Object.create(null)

// 订阅函数集合,Vuex 提供了 substcribe 功能,用来存储所有对 mutation 变化的订阅者
this._subscribers = []

// 一个 Vue 对象的实例,主要是利用 Vue 实例方法 $watch 来观测变化
this._watcherVM = new Vue()

new ModuleCollection(options)

调用 new Vuex.Store(options) 时传入的 options 对象,用于构造 MoudleCollection

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
// module/module-collection.js
class ModuleCollection {
constructor(rawRootModule) {

// 将传入的 options 对象整个构造为一个 module 对象
// 并循环调用 register() 方法为其中的 modules 属性进行模块注册,使其都成为 module 对象
// 最后 options 对象被构造成一个完整的组件树

// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}

// ...

register(path, rawModule, runtime = true) {

// ...

// 分割模块的情况
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)
})
}
}
}

// util.js
export function forEachValue(obj, fn) {
Object.keys(obj).forEach(key => fn(obj[key], key))
}

然后绑定 dispatchcommit 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 把 Store 类的 dispatch 和 commit 方法的 this 指针指向当前 store 的实例上

// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this

// 封装替换原型中的 dispatch 和 commit 方法,详细解释见下方
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)
}

// 是否开启严格模式
this.strict = strict

// state 没有放在上面 options 对象中初始化了,改为在 module/module.js 下初始化
// 详细可见 module/module.js
const state = this._modules.root.state

dispatch 和 commit 方法

dispatch

dispatch 的功能是触发并传递一些参数(payload)给对应 typeaction,因为支持两种调用方式,所以在 dispatch 中,先进行参数的适配处理,然后判断 action type 是否存在,若存在就逐个执行

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
dispatch(_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload) // 配置参数处理

const action = { type, payload }

// 当前 type 下所有 action 处理函数集合
const entry = this._actions[type]
if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}

// 订阅者函数遍历执行,传入当前的 action 对象和当前的 state
this._actionSubscribers.forEach(sub => sub(action, this.state))

return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}

commit

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
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 (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}

// 专用修改 state 方法,其余修改 state 的方法均为非法修改
this._withCommit(() => {
entry.forEach(function commitIterator(handler) {
handler(payload)
})
})

// 同上,传入参数的不同
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`
)
}
}

初始化核心 - Store

1
2
3
4
5
6
7
8
9
10
11
// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)

// apply plugins
plugins.forEach(plugin => plugin(this))

上面这三个方法就是 store 的核心内容了

  • installModule 主要完成模块的 statemutationsactionsgetters 的注册工作
  • resetStoreVM 这个方法是对 stategetters 进行最后的使用处理,从而用户可以调用这些状态
  • plugins 这个不用多说,应用插件

接下来会详细介绍 store 中的三个核心方法 installModuleresetStoreVMplugins

installModule

installModule 的作用主要是初始化组件树根组件,注册所有子组件,并将其中所有的 getters 存储到 this._wrappedGetters 属性中

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
// 接收五个参数
// store 当前 Store 的实例
// rootState 根 state
// path 当前嵌套模块的路径数组
// module 当前安装的模块
// hot 当动态改变 modules 或者热更新的时候为 true
function installModule(store, rootState, path, module, hot) {

// 判断是否为根
const isRoot = !path.length

/**
* 我们在构造函数中调用的时候为 installModule(this, state, [], options)
* 所以这里的 path.length 为空,所以 isRoot 为 true
* 而 module 自然就是传递进来的 options
* 即 state,actions,mutations,getters 和嵌套的 modules
*/

// 新增的模块命名空间
const namespace = store._modules.getNamespace(path)

// register in namespace map
// 是否设置了命名空间,若存在则在 namespace 中进行 module 的存储
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}

// set state
// 非根组件设置 state 方法
if (!isRoot && !hot) {
// 在不是根组件且不是 hot 条件的情况下
// 通过 getNestedState 方法拿到该 module 父级的 state
/**
// 根据 path 查找 state 上的嵌套 state
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
*/
const parentState = getNestedState(rootState, path.slice(0, -1))

// 由于模块的 path 是根据模块的名称 concat 连接的
// 所以 path 的最后一个元素就是当前模块的模块名
// 然后拿到其所在的 moduleName
const moduleName = path[path.length - 1]

/**
_withCommit (fn) {
// 保存之前的提交状态
const committing = this._committing

// 进行本次提交,若不设置为 true,直接修改 state
// 在 strict 模式下,Vuex 将会产生非法修改 state 的警告
this._committing = true

// 执行 state 的修改操作
fn()

// 修改完成后还原本次修改之前的状态
this._committing = committing
}
*/
// 一个代理方法,Vuex 中所有触发 mutation 的进行 state 的修改操作都会经过它
// 保证同步修改 state 的过程中 this._committing 的值始终为 true
// 可以统一管理监控 state 状态的修改
// 这样当我们观测 state 的变化的时候,如果 this._committing 不为 true,则说明这个状态的修改是有问题的

// 拿到 parentState 和 moduleName
// 然后利用 Vue.set 方法将 state 设置到父级 state 对象的 moduleName 属性中
// 由此实现该模块的 state 注册
// 注:如果是首次执行,因为是根目录注册,所以并不会执行该条件中的方法
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}

// module 上下文环境设置
// 命名空间和根目录条件判断完毕后,接下来定义 local 变量和 module.context 的值
// makeLocalContext 方法会为该 module 设置局部的 dispatch,commit 方法以及 getters 和 state(namespace 的存在需要做兼容处理)
const local = module.context = makeLocalContext(store, namespace, path)

// 分别对 mutations,actions,getters 进行注册,见下

// 注册对应模块的 mutation,供 state 修改使用
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})

// 注册对应模块的 action,供数据操作、提交 mutation 等异步操作使用
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})

// 注册对应模块的 getters,供 state 读取使用
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})

// 这个是 Store 中的 Module
// 需要注意这里传入的 path 参数是不为空的
// 注册完了根组件的 actions、mutations 以及 getters 后,递归调用自身
// 为子组件注册其 state,actions、mutations 以及 getters 等
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}

mutations、actions 以及 getters 注册

定义 local 环境后,循环注册我们在 options 中配置的 action 以及 mutation 等,逻辑关系见下图

registerMutation

简单来说,就是获取 store 中的对应的 mutation type 的处理函数集合,将新的处理函数 push 进去,这里对设置在 mutation type 上对应的 handler 进行封装,给原函数传入 state,所以在执行比如 commit('xxx', payload) 的时候,typexxxmutation 的所有 handler 都会接收到 state 以及 payload,这就是在 handler 里面拿到 state 的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 接收四个参数
// store 为当前 Store 实例
// type 为 mutation 的 type
// handler 为 mutation 执行的回调函数,作用就是同步修改当前模块的 state
// local 为当前模块的路径
function registerMutation(store, type, handler, local) {
// 通过 type 拿到对应的 mutation 对象数组
const entry = store._mutations[type] || (store._mutations[type] = [])

// 将 mutation 包装成函数 push 到数组当中,同时添加载荷 payload 参数
// 所以 commit 实际调用的不是我们传入的 handler,而是经过封装的
entry.push(function wrappedMutationHandler(payload) {

// 调用 handler 并将 state 传入
handler.call(store, local.state, payload)
})
}

registerAction 和 registerGetter

actiongetter 的注册也是同理,唯一区别就是 action handlermutation handler 以及 getter wrapper 多拿到 dispatchcommit 操作方法,因此 action 可以进行 dispatch actioncommit mutation 操作,registerAction 函数是对 storeaction 的初始化,它和 registerMutation 的参数一致,不同的地方在于,mutation 是同步修改当前模块的 state,而 action 是可以异步的通过提交一个 mutation 去修改 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
function registerAction(store, type, handler, local) {

// 通过 type 拿到对应的 actions 对象数组
const entry = store._actions[type] || (store._actions[type] = [])

// 存储新的封装过的 action handler
entry.push(function wrappedActionHandler(payload, cb) {

// 传入 state 等对象供我们之前的 action handler 使用
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)

/*
export function isPromise (val) {
return val && typeof val.then === 'function'
}
*/
// 兼容 Promise
if (!isPromise(res)) {
res = Promise.resolve(res)
}

// 判断 store._devtoolHook,只有当用到 Vuex devtools 开启的时候,才能捕获 Promise 过程
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}

})
}

function registerGetter(store, type, rawGetter, local) {

// getters 只允许存在一个处理函数,若重复需要报错
if (store._wrappedGetters[type]) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}

// 存储新的封装过的 getters 处理函数
store._wrappedGetters[type] = function wrappedGetter(store) {

// 为之前的 getters 传入对应的状态
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}

}

resetStoreVM

执行完各 moduleinstall 之后,执行 resetVM 方法,进行 store 组件的初始化,本质上,Vuex 其实构建的就是一个名为 storevm 组件,所有配置的 stateactionsmutations 以及 getters 都是其组件的属性,所有的操作都是对这个 vm 组件进行的

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
function resetStoreVM(store, state, hot) {

// 缓存前 vm 组件,保留 state 树
const oldVm = store._vm

// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}

// 循环所有处理过的 getters,并新建 computed 对象进行存储
// 通过 Object.defineProperty 方法为 getters 对象建立属性
// 使得我们可以通过 this.$store.getters.xxxgetter 能够访问到该 getters
// store._vm[xxxgetter] ==> computed[xxxgetter] ==> xxxgetter 对应的回调函数
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
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
const silent = Vue.config.silent

// 暂时设置为静默模式,避免因而加载某些插件触发的警告
Vue.config.silent = true

// 设置新的 storeVm,将当前初始化的 state 以及 getters 作为 computed 属性(刚刚遍历生成的)
store._vm = new Vue({
data: {
$$state: state
},
computed
})

// 恢复 Vue 的模式
Vue.config.silent = silent

// enable strict mode for new vm
if (store.strict) {
// 该方法对 state 执行 $watch 以禁止从 mutation 外部修改 state
enableStrictMode(store)
}

// 若不是初始化过程执行的该方法,将旧的组件 state 设置为 null
// 强制更新所有监听者(watchers),待更新生效
// DOM 更新完成后,执行 vm 组件的 destroy 方法进行销毁,减少内存的占用
if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}


// 监视 state 的变化,如果没有通过 this._withCommit() 方法进行 state 修改,则报错
function enableStrictMode(store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
if (process.env.NODE_ENV !== 'production') {
assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
}
}, { deep: true, sync: true })
}

plugin 注入

最后就是 plugin 的注入

1
2
// apply plugins
plugins.forEach(plugin => plugin(this))

源码整体汇总

最后,我们在将之前介绍的一些零散的源码来进行汇总,结果如下

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
// 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

// 源码中所涉及到的方法

/*
// store.js 最后导出部分
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)
}

// mixin.js
export default function (Vue) {
const version = Number(Vue.version.split('.')[0])

if (version >= 2) {
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)
}
}

function vuexInit() {
const options = this.$options
// 将初始化 Vue 根组件时传入的 store 设置到 this 对象的 $store 属性上
// 子组件从其父组件引用 $store 属性,层层嵌套进行设置
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
}
}
}
*/


// 判断若处于浏览器环境下且加载过 Vue,则执行 install 方法
// 见上方 install ==> applyMixin ==> mixin.js 中的 vuexInit
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}

// 使用断言函数,
// 确保 Vue 的存在,也就是在实例化 Store 之前,必须要保证之前的 install 方法已经执行过
// 另外一点就是需要支持 Promise 语法,因为 Vuex 是依赖 Promise 的
if (process.env.NODE_ENV !== 'production') {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
assert(this instanceof Store, `Store must be called with the new operator.`)
}

// 利用解构赋值,拿到 options 里面的 plugins 和 strict
// plugins 表示应用的插件
// strict 表示是否开启严格模式
const {
plugins = [],
strict = false
} = options

// store internal state
// 是否在进行提交状态标识,作用是保证对 Vuex 中 state 的修改只能在 mutation 的回调函数中
// 而不能在外部随意修改 state
this._committing = false

// 存储用户定义的所有的 actions
this._actions = Object.create(null)
this._actionSubscribers = []

// mutations
this._mutations = Object.create(null)

// 封装后的 getters 集合对象
this._wrappedGetters = Object.create(null)


/*
// module/module-collection.js
class ModuleCollection {
constructor(rawRootModule) {

// 将传入的 options 对象整个构造为一个 module 对象
// 并循环调用 register() 方法为其中的 modules 属性进行模块注册,使其都成为 module 对象
// 最后 options 对象被构造成一个完整的组件树

// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}

// ...

register(path, rawModule, runtime = true) {

// ...

// 分割模块的情况
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

// util.js
export function forEachValue (obj, fn) {
Object.keys(obj).forEach(key => fn(obj[key], key))
}

if (rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
}
*/

// 用于支持 store 分模块传入,存储分析后的 modules
// ModuleCollection 见上方
this._modules = new ModuleCollection(options)

// 模块命名空间 map
this._modulesNamespaceMap = Object.create(null)

// 订阅函数集合,Vuex 提供了 substcribe 功能,用来存储所有对 mutation 变化的订阅者
this._subscribers = []

// 一个 Vue 对象的实例,主要是利用 Vue 实例方法 $watch 来观测变化
this._watcherVM = new Vue()


// bind commit and dispatch to self
// 把 Store 类的 dispatch 和 commit 方法的 this 指针指向当前 store 的实例上
const store = this
const { dispatch, commit } = this

// 封装替换原型中的 dispatch 和 commit 方法,详细解释见下方
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)
}

// strict mode
// 是否开启严格模式
this.strict = strict

// state 没有放在上面 options 对象中初始化了,改为在 module/module.js 下初始化,见上面 ①
// 详细可见 module/module.js
const state = this._modules.root.state

/*
下面这三个方法就是 Store 的三个核心方法
*/

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
// 见下方
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
resetStoreVM(this, state)

// apply plugins
plugins.forEach(plugin => plugin(this))

if (Vue.config.devtools) {
devtoolPlugin(this)
}
}

/*
==============================================

下面这些就是一些内部方法或者是向外部暴露的方法(API)

==============================================
*/


get state() {
return this._vm._data.$$state
}

set state(v) {
if (process.env.NODE_ENV !== 'production') {
assert(false, `Use store.replaceState() to explicit replace store state.`)
}
}

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 (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(_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload) // 配置参数处理

const action = { type, payload }

// 当前 type 下所有 action 处理函数集合
const entry = this._actions[type]

if (!entry) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}

// 订阅者函数遍历执行,传入当前的 action 对象和当前的 state
this._actionSubscribers.forEach(sub => sub(action, this.state))

return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}

subscribe(fn) {
return genericSubscribe(fn, this._subscribers)
}

subscribeAction(fn) {
return genericSubscribe(fn, this._actionSubscribers)
}

// watch 作用是响应式的监测一个 getter 方法的返回值,当值改变时调用回调
// getter 接收 store 的 state 作为唯一参数
watch(getter, cb, options) {
if (process.env.NODE_ENV !== 'production') {
assert(typeof getter === 'function', `store.watch only accepts a function.`)
}
// 函数首先断言 watch 的 getter 必须是一个 func
// 接着利用了 this._watcherVM 的 $watch 方法,观测 getter 方法返回值的变化
// 如果有变化则调用 cb 函数,回调函数的参数为新值和旧值
// watch 方法返回的是一个方法,调用它则取消观测
return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}

// 替换整个 rootState(一般用于调试)
// 调用 this._withCommit 方法修改 Store 的 rootState
// 因为不能在 mutation 的回调函数外部去改变 state
replaceState(state) {
this._withCommit(() => {
this._vm._data.$$state = state
})
}

// 动态注册一个模块 installModule
registerModule(path, rawModule, options = {}) {

// 判断 path ==> 转为 Array
if (typeof path === 'string') path = [path]

if (process.env.NODE_ENV !== 'production') {
assert(Array.isArray(path), `module path must be a string or an Array.`)
assert(path.length > 0, 'cannot register the root module by using registerModule.')
}

this._modules.register(path, rawModule)
installModule(this, this.state, path, this._modules.get(path), options.preserveState)
// reset store to update getters...
// 这个方法在下面
resetStoreVM(this, this.state)
}

// 与上面那个方法相对应,注销一个动态模块
unregisterModule(path) {
if (typeof path === 'string') path = [path]

if (process.env.NODE_ENV !== 'production') {
assert(Array.isArray(path), `module path must be a string or an Array.`)
}

this._modules.unregister(path)
this._withCommit(() => {
const parentState = getNestedState(this.state, path.slice(0, -1))
Vue.delete(parentState, path[path.length - 1])
})
resetStore(this)
}

// 热加载新的 action 和 mutation
hotUpdate(newOptions) {
this._modules.update(newOptions)
resetStore(this, true)
}

_withCommit(fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
}

function genericSubscribe(fn, subs) {
if (subs.indexOf(fn) < 0) {
subs.push(fn)
}
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}

// 重置 store 对象,然后再次安装 Module 对应的属性
function resetStore(store, hot) {
store._actions = Object.create(null)
store._mutations = Object.create(null)
store._wrappedGetters = Object.create(null)
store._modulesNamespaceMap = Object.create(null)
const state = store.state

// init all modules
// 最后一个参数传入 true,即 hot 参数为 true
// 这样会在 installModule 当中的 if (!isRoot && !hot) { ... } 的逻辑便不会执行
// 由于 hot 为 true,就不会重新对状态树做设置,则 state 会保持不变
// 因为已经明确的删除了对应 path 下的 state 了,要做的事情只不过就是重新注册一遍 muations、actions 以及 getters
installModule(store, state, [], store._modules.root, true)
// reset vm

// 重置 Store 的 _vm 对象
resetStoreVM(store, state, hot)
}

function resetStoreVM(store, state, hot) {
const oldVm = store._vm

// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
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
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
if (store.strict) {
enableStrictMode(store)
}

if (oldVm) {
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}

/*
接收五个参数
store,当前 Store 的实例
rootState,根 state
path,当前嵌套模块的路径数组
module,当前安装的模块
hot,当动态改变 modules 或者热更新的时候为 true
*/
function installModule(store, rootState, path, module, hot) {

// 判断是否为根
const isRoot = !path.length

/**
* 我们在构造函数中调用的时候为 installModule(this, state, [], options)
* 所以这里的 path.length 为空,所以 isRoot 为 true
* 而 module 自然就是传递进来的 options
* 即 state,actions,mutations,getters 和嵌套的 modules
*/

// 新增的模块命名空间
const namespace = store._modules.getNamespace(path)

// register in namespace map
// 是否设置了命名空间,若存在则在 namespace 中进行 module 的存储
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}

// set state
// 非根组件设置 state 方法
if (!isRoot && !hot) {
// 在不是根组件且不是 hot 条件的情况下
// 通过 getNestedState 方法拿到该 module 父级的 state
/**
// 根据 path 查找 state 上的嵌套 state
function getNestedState (state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}
*/
const parentState = getNestedState(rootState, path.slice(0, -1))

// 由于模块的 path 是根据模块的名称 concat 连接的
// 所以 path 的最后一个元素就是当前模块的模块名
// 然后拿到其所在的 moduleName
const moduleName = path[path.length - 1]

/**
_withCommit (fn) {
// 保存之前的提交状态
const committing = this._committing

// 进行本次提交,若不设置为 true,直接修改 state
// 在 strict 模式下,Vuex 将会产生非法修改 state 的警告
this._committing = true

// 执行 state 的修改操作
fn()

// 修改完成后还原本次修改之前的状态
this._committing = committing
}
*/
// 一个代理方法,Vuex 中所有触发 mutation 的进行 state 的修改操作都会经过它
// 保证同步修改 state 的过程中 this._committing 的值始终为 true
// 可以统一管理监控 state 状态的修改
// 这样当我们观测 state 的变化的时候,如果 this._committing 不为 true,则说明这个状态的修改是有问题的

// 拿到 parentState 和 moduleName
// 然后利用 Vue.set 方法将 state 设置到父级 state 对象的 moduleName 属性中
// 由此实现该模块的 state 注册
// 注:如果是首次执行,因为是根目录注册,所以并不会执行该条件中的方法
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}

// module 上下文环境设置
// 命名空间和根目录条件判断完毕后,接下来定义 local 变量和 module.context 的值
// makeLocalContext 方法会为该 module 设置局部的 dispatch,commit 方法以及 getters 和 state(namespace 的存在需要做兼容处理)
const local = module.context = makeLocalContext(store, namespace, path)

// 分别对 mutations,actions,getters 进行注册,见下

// 注册对应模块的 mutation,供 state 修改使用
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})

// 注册对应模块的 action,供数据操作、提交 mutation 等异步操作使用
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
registerAction(store, type, handler, local)
})

// 注册对应模块的 getters,供 state 读取使用
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})

// 这个是 Store 中的 Module
// 需要注意这里传入的 path 参数是不为空的
// 注册完了根组件的 actions、mutations 以及 getters 后,递归调用自身
// 为子组件注册其 state,actions、mutations 以及 getters 等
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}

/**
* make localized dispatch, commit, getters and state
* if there is no namespace, just use root ones
*/
function makeLocalContext(store, namespace, path) {
const noNamespace = namespace === ''

const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args

if (!options || !options.root) {
type = namespace + type
if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
return
}
}

return store.dispatch(type, payload)
},

commit: noNamespace ? store.commit : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args

if (!options || !options.root) {
type = namespace + type
if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
return
}
}

store.commit(type, payload, options)
}
}

// getters and state object must be gotten lazily
// because they will be changed by vm update
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})

return local
}

function makeLocalGetters(store, namespace) {
const gettersProxy = {}

const splitPos = namespace.length
Object.keys(store.getters).forEach(type => {
// skip if the target getter is not match this namespace
if (type.slice(0, splitPos) !== namespace) return

// extract local getter type
const localType = type.slice(splitPos)

// Add a port to the getters proxy.
// Define as getter property because
// we do not want to evaluate the getters in this time.
Object.defineProperty(gettersProxy, localType, {
get: () => store.getters[type],
enumerable: true
})
})

return gettersProxy
}

/*
接收四个参数
store 为当前 Store 实例
type 为 mutation 的 type
handler 为 mutation 执行的回调函数,作用就是同步修改当前模块的 state
local 为当前模块的路径
*/
function registerMutation(store, type, handler, local) {
// 通过 type 拿到对应的 mutation 对象数组
const entry = store._mutations[type] || (store._mutations[type] = [])

// 将 mutation 包装成函数 push 到数组当中,同时添加载荷 payload 参数
// 所以 commit 实际调用的不是我们传入的 handler,而是经过封装的
entry.push(function wrappedMutationHandler(payload) {

// 调用 handler 并将 state 传入
handler.call(store, local.state, payload)
})
}

function registerAction(store, type, handler, local) {

// 通过 type 拿到对应的 actions 对象数组
const entry = store._actions[type] || (store._actions[type] = [])

// 存储新的封装过的 action handler
entry.push(function wrappedActionHandler(payload, cb) {

// 传入 state 等对象供我们之前的 action handler 使用
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)

/*
export function isPromise (val) {
return val && typeof val.then === 'function'
}
*/
// 兼容 Promise
if (!isPromise(res)) {
res = Promise.resolve(res)
}

// 判断 store._devtoolHook,只有当用到 Vuex devtools 开启的时候,才能捕获 Promise 过程
if (store._devtoolHook) {
return res.catch(err => {
store._devtoolHook.emit('vuex:error', err)
throw err
})
} else {
return res
}

})
}

function registerGetter(store, type, rawGetter, local) {

// getters 只允许存在一个处理函数,若重复需要报错
if (store._wrappedGetters[type]) {
if (process.env.NODE_ENV !== 'production') {
console.error(`[vuex] duplicate getter key: ${type}`)
}
return
}

// 存储新的封装过的 getters 处理函数
store._wrappedGetters[type] = function wrappedGetter(store) {

// 为之前的 getters 传入对应的状态
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
}

function enableStrictMode(store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
if (process.env.NODE_ENV !== 'production') {
assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
}
}, { deep: true, sync: true })
}

function getNestedState(state, path) {
return path.length
? path.reduce((state, key) => state[key], state)
: state
}

function unifyObjectStyle(type, payload, options) {
if (isObject(type) && type.type) {
options = payload
payload = type
type = type.type
}

if (process.env.NODE_ENV !== 'production') {
assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)
}

return { type, payload, options }
}

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)
}

参考

# Vue

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×