认识Store构造函数

在使用vuex的时,我们实际上是实例化Store类,然后传入一个对象,这个对象包括我们定义好的state、getters、mutations、actions等,当然大点的项目还会有modules。

这一节我们就来看看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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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
if (!Vue && typeof window !== 'undefined' && window.Vue) {
//通过浏览器script标签使用vuex时自动安装
install(window.Vue)
}

if (process.env.NODE_ENV !== 'production') {
//断言一些使用环境,必须使用vue.use(vuex),必须存在Promise,必须使用new操作符
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.`)
}

const {
plugins = [],
strict = false
} = options

// store internal state
//初始化需要使用到的变量
this._committing = false
this._actions = Object.create(null)
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()

// bind commit and dispatch to self
//将dispatch和commit方法的this指针绑定到store上,防止被修改
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)
}

// 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
//处理的核心,包括处理根module、action、mutation、getters和递归注册子module
installModule(this, state, [], this._modules.root)

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
//也是重点,使用vue实例来保存state和getter
resetStoreVM(this, state)

// apply plugins
//插件注册和使用,内置的有devtool和logger
plugins.forEach(plugin => plugin(this))

const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}

我们来简要理一理这个构造过程,先是使用assert断言一下运行环境,然后初始化需要用到的变量,再就是模块安装installModule和组件初始化resetStoreVM,最后注册和使用插件。下面我们一一展开。

先是assert断言运行环境,这是一种很好的写法。assert函数在util.js文件中。这里断言必须先调用Vue.use(Vuex)。必须提供Promise,应该是为了让Vuex的体积更小,让开发者自行提供Promise的polyfill,一般我们可以使用babel-runtime或者babel-polyfill引入。最后断言必须使用new操作符调用Store函数。

1
2
3
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.`)

然后是一些内部变量的初始化,下面我们先对这些变量的作用进行下说明便于后面的理解。

1
2
3
4
5
6
7
8
9
this._committing = false
this._actions = Object.create(null)
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()

_committing提交状态的标志,在_withCommit中,当使用mutation时,会先赋值为true,再执行mutation,修改state后再赋值为false,
在这个过程中,会用watch监听state的变化时是否_committing为true,从而保证只能通过mutation来修改state

_actions用于保存所有action,里面会先包装一次
_actionSubscribers用于保存订阅action的回调
_mutations用于保存所有的mutation,里面会先包装一次
_wrappedGetters用于保存包装后的getter
_modules用于保存一棵module树
_modulesNamespaceMap用于保存namespaced的模块
_subscribers用于存储所有对 mutation 变化的订阅者
_watcherVM Vue对象的实例,主要是利用 Vue 实例方法 $watch 来观测变化的

这里提一下this._modules = new ModuleCollection(options)对模块module进行收集的逻辑。实际上是调用了module目录下的ModuleCollection类和Module类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//module目录下的moduleCollection.js
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
/** 省略 **/
}

//module目录下的module.js
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
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
}
/** 省略 **/
}

这里调用ModuleCollection构造函数,首先进行根module的注册,然后递归遍历所有的module,子module 添加到父module的_children属性上,最终形成一棵树。

再就是模块安装installModule函数,这里是module的处理核心,包括处理根module、命名空间、action、mutation、getters和递归注册子module。

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
function installModule (store, rootState, path, module, hot) {
const isRoot = !path.length
const namespace = store._modules.getNamespace(path)

// register in namespace map
//保存namespaced模块
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}

// set state
//非根组件设置state,根据path获取父state
if (!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
store._withCommit(() => {
Vue.set(parentState, moduleName, module.state)
})
}

//设置module的上下文,从而保证mutation和action的第一个参数能拿到对应的state getter等
const local = module.context = makeLocalContext(store, namespace, path)

//逐一注册mutation
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)
})

//逐一注册子module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}