版本,v3.6.0。主要分析 new Vuex.store(options) 内部机制。
install
先看下 Vuex 的导出:
const index = {
Store,
install,
version: '3.6.0',
mapState,
mapMutations,
mapGetters,
mapActions,
createNameSpacedHelpers,
createLogger
}
export default index
export { Store, createLogger, createNamespacedHelpers, install, mapActions, mapGetters, mapMutations, mapState }
默认导出一个对象(即我们在日常开发中引入的 Vuex),对象的每个属性又分别具名导出。本篇只说 install 和 Store。
我们知道 Vue 的插件机制就在于插件的 install 方法,让 Vuex.use(Vue) 起作用的就是 install:
let Vue
function install(_Vue) {
if(Vue && _Vue === Vue) {
// 已安装过
return
}
Vue = _Vue
applyMixin(Vue)
}
function applyMixin(Vue) {
const vserion = Number(Vue.verion.split('.')[0])
if(version >= 2) {
// 全局 mixin 一个 beforeCreate hook
Vue.mixin({ beforeCreate: vuexInit })
} else {
// 兼容 v2 以前的版本
}
}
function vuexInit() {
const options = this.$options
// 为根组件实例添加 $store 属性
if(options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if(options.parent && options.parent.$store) {
// 为非根组件实例添加 $store 属性
this.$store = options.parent.$store
}
}
Vuex 插件的作用就是为每个 vm 实例添加 $store 属性,如果稍有留心的话,会发现其为每个组件添加同一个属性的方式在 Vue、Vue-router 中都是这样:根组件则直接赋值,非根组件则赋值为其父组件的对应属性值,这样下来从根到叶子,所有的实例都不会遗漏。
基本概念
在正式探索 new Store 之前,先介绍源码中的几个基本概念:
-
rawModule:具有 namespaced、state、getters、mutaions、actions、modules 的对象,即可称为 rawModule
-
module:由构造函数
new Module(rawModule, runtime)生成,拥有以下几个属性:- runtime:布尔类型,为
true与false的本质区别在于后者不能被 unregister - _children:保存着子 module 的对象,属性名为
- _rawModule:指向传入构造器的 rawModule
- state:
rawState.state或者rawState.state()的返回值 - namespaced:访问器属性,值为
!!rawModule.namespaced。一个 module 是否是 namespaced 的与否的区别在于这个属性会影响这个 module 的namespace值(起这个值的方法为ModuleColletion.prototype.getNamespace),这个值会对这个 module 的 getters、mutaions、actions 以及其 modules 的注册都有影响。一个 module 的 state 则不受之影响,或者应该说,对于 state 而言,namespaced 是默认为true且无法修改的。
以及一些对其
_chidlren增删改查、遍历的方法和对 _rawModule 的 getters、mutaions、actions 属性遍历的方法 - runtime:布尔类型,为
-
path:每个 module 的唯一标识,值为一个数组,比如根 module 的 path 为
[],根 module 的子 moduleA 的 path 为['moduleA'],moduleA 的子 module1 的 path 为['moduleA', 'module1'],依次类推。 -
moduleCollection:以 module 为节点组成 module 树,树的根为由以 storeOptions 为 rootRawModule 生成的 module,根 module 存储在 moduleCollection 的 root 属性。除此之外,moduleCollection 有通过 module 的 path 获取对应 module 和其 namespace 的方法以及注册、注销、更新 module 的方法。
明晰基本概念之后,new Store 就不难理解。
new Store
class Store {
constructor(options = {}) {
// 这个 options 也是 rawRootModule,只不过多了 plugins、strict、devtools 属性
// strict 默认为 false,为 true 时会检验 state 树中每个属性的变化是否是 commit 一个 mutaion 导致,很消耗性能
const { plugins = [], strict = false} = options
// 上面说的 strict 为 true 时就是依靠 _commiting 属性来检验,commit 一个 mutaion 时,_committing 一定是 true 的,所以当 state 树中属性的 setter 被触发时,查询 _committing 值是否是 true 即可检验
this._committing = false
// 所有的 module 的 getters 均会被以其 namespace 为 key 注册到此,对应的 value 为对 getters 包装后的函数
this._wrappedGetters = Object.create(null)
// 用于缓存 namespaced 的 module 下的 getters,不必访问时每次都求值
this._makeLocalGettersCache = Object.create(null)
// 所有 module 的 mutaion 都会被以其 namespace 为 key 注册到此,对应的 value 为所有相同 namespace 的 mutaion 对应的 handler
this._mutaions = Object.create(null)
// 订阅 mutaion 的回调
this._subscibers = []
// 同 _mutaions 和 _subscribers
this._actions = Object.create(null)
this._actionSubscibers = []
// 从根 module 开始,递归建立 module 树
this._modules = new ModuleCollection(options)
// 存储所有 namepaced module,namepace -> module 的 map
this._modulesNamespaceMap = Object.create(null)
// 用于实现 store.watch api
this._watcherVM = new Vue()
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch(type, payload) {
// 绑定了当前 store 实例为 this 值的 dispatch 函数
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit(type, payload, options) {
// 绑定了当前 store 实例为 this 值的 commit 函数
return commit.call(store, type, payload, options)
}
// 是否开启 strict 模式
this.strict = strict
// 拿到根 module 的 state,然后往上面添加子 module 的 state,最终构建整个 state 树
const state = this._modules.root.state
// 从 root module 开始,递归 init:建立 state 树,
// 注册 getters、mutaions、actions
// 建立 state 树的结果就是,我们可以通过 store.state 访问到任何一个 module 的 state
// 注册那三个的结果就是,在别处调用 store.getters、store.commit、store.dispatch 时可以起作用,注册的结果体现在 _wrappedGetters、_makeLocalGettersCache、_mutaions、_actions 等属性里
installModule(this, state, [], this._modules.root)
// 使 state 和 getters 变为响应式,途径就是将前者当做 options.data.$$state 后者当做 options.computed 传入 new Vue 生成一个 vm 实例赋给 store._vm
resetStoreVM(this, state)
// 加载插件
plugins.forEach(plugin => plugin(this))
}
get state() {
return this._vm._data.$$state
}
// commit 时的 hanlder 最终都会在这里的 f 中得到执行,所以这是 strict mode 实现的关键
_withCommit(f) {
const c = this.committing
this.committing = true
f()
this.committing = c
}
// 其他的一些 api
}
综上,new Vuex.store(options) 做的事情:
- 初始化属性、方法和一些内部状态
- 创建 moduleCollection 实例(即递归创建 module 树)
- 递归初始化每个 rawModule 的属性:创建 state 树,注册 getters、mutations、actions
- 将 state 和 getters 当做 options 的属性传入
new Vue()使之变为响应式 - 加载 plugins
比较关键的是 2、3、4 三步:
new ModuleCollection 和 new Module
class ModuleCollection {
constructor(rawRootModule) {
// 从根开始,递归 register 每一个 rawModule,最终建立一个 module 树
this.register([], rawRootModule, false)
}
// 这个 path 即前面所说的代表每个 module 的唯一标识
register(path, rawModule, runtime = true) {
// 通过 rawModule 创建 module
const newModule = new Module(rawModule, runtime)
if(!path.length) {
// 根 module
this.root = newModule
} else {
// 非根 module,添加到其父 module 的 _children 属性里
const parent = this.get(path.slice(0, -1))
parent.addChild(path[path.length - 1], newModule)
}
// 如果 rawModule 存在子 module,递归 register 它们
if(rawModule.modules) {
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)
})
}
}
// 通过给定 path 数组,返回与之对应的 module
get(path) {
return path.reduce((module, key) => module.getChild(key), this.root)
}
// 通过给定 path 数组,得到与之对应的 module 的 namespace
getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
}
class Module {
constructor(rawModule, runtime) {
// runtime 的作用是,只有 runtime 为 true 的 module 可以被 unregister
this.runtime = runtime
this._children = Object.create(null)
this._rawModule = rawModule
this.state = (typeof rawModule.state === 'function' ? rawModule.state() : rawModule.state) || {}
}
get namespaced() {
return !!this._rawModule.namespaced
}
// 只在 store.hotUpdate 时候调用
update(rawModule) {
// child module 单独处理
this._rawModule.namespaced = rawModule.namespaced
if(rawModule.getters) {
this._rawModule.gettters = rawModule.getters
}
if(rawModule.actions) {
this._rawModule.actions = rawModule.actions
}
if(rawModule.mutations) {
this._rawModule.mutations = rawModule.mutations
}
}
// ...
// 其他对 _chidlren 增删查的方法
forEachChild (fn) {
// _children 的属性值是 module
// _rawModule.module 的属性值是 _rawModule
forEachValue(this._children, fn)
}
forEachGetters(fn) {
forEachValue(this._rawModule.getters, fn)
}
// ...
// 其他通过 forEachValue 遍历 _rawModule 的 mutaions、actions 的方法
}
// 接受两个参数,第一个参数为对象
// 第二个参数为一个以第一个参数的 (value, key) 为参数的函数
function forEachValue(obj, fn) {
Object.keys(obj).forEach(key => fn(obj[key], key)))
}
可见,new ModuleCollection(options) 的作用就是创建 module 树,这个树只有一个 root 属性指向它自己。另外通过 getNamespace(path) 计算 namespace 的方法,我们可以得知:
- 一个 namespaced 的 module 会影响其所有子 module 的 namespace,这些子 module 的 namespace 必然包含其父 module 的 namespace
- 一个非 namepaced 的子 module,其 namespace 值与离其最近的 namespaced 的祖先 module 的 namespace 相同,如果不存在这样的祖先 module,那么就与根 module 的 namespace 相同,也就是
''
module 的那些遍历方法则在 installModule 时用到。
installModule
function installModule(store, rootState, path, module, hot) {
const isRoot = !path.length
// 获取对应 module 的 namespace
const namespace = store._modules.getNamespace(path)
if(module.namespaced) {
// 对于有着相同 namespace 的 namespaced 的 module,后注册的会覆盖
store._modulesNamespaceMap[namespace] = module
}
if(!isRoot && !hot) {
const parentState = getNestedState(rootState, path.slice(0, -1))
const moduleName = path[path.length - 1]
// 将自身的 state 添加到父 module 的 state 上,即创建 state 树
store._withCommit(() => Vue.set(parentState, moduleName, module.state))
}
// local 为当前 module 自身的 state、getters 和添加了 namespace 的 commit 和 dispatch 的方法组成的对象
const local = module.context = makeLocalContext(store, namespace, path)
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)
})
// 递归 install 子 module
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
function makeLocalContext(store, namespace, path) {
const noNamespace = namespace === ''
// namespace 不为 '' 时为 type 增加 namespace
const local = {
commit: noNamespace ? store.commit : function(_type, _payload, _options) {
let { payload, options, type } = unifyObjectStyle(_type, _payload, _options)
if(!options || !options.root) {
type = `${namespace}${type}`
}
store.commit(type, payload, options)
},
dispatch: noNamespace ? store.dispatch : function(_type, _payload, _options) {
let { payload, type, options } = unifyObjectStyle(_type, _payload, _options)
if(!options || !options.root) {
type = `${namespace}${type}`
}
}
return store.dispatch(type, payload)
}
Object.defineProperties(local, {
getters: {
get: noNamespace ?
function() { return store.getters } :
// makeLocalGetters 将 getter 定义为返回 store.getters[namespace] 的访问器属性
function() { return makeLocalGetters(store, namespace) }
},
state: {
get: function() { return getNestedState(store.state, path) }
}
})
}
function registerGetter(store, type, rawGetter, local) {
if(store._wrappedGetters[type]) {
return
}
// wrap 每个 module 的 rawGetter,然后按照其 namespace 存到 store._wrappedGetters 里
// 为什么 wrapped 函数要以 store 为参数,store 不是已经传进来了?
store._wrappedGetters[type] = function wrappedGetter(store) {
// 传给每个 getter 的 4 个参数,对于非 namespaced,两个就够
return rawGetter(local.state, local.getters, store.state, store.getters)
}
}
function registerMutation(store, type, handler, local) {
const entry = store._mutations[type] || (store._mutations[type] = [])
// 将每个 namespace 对应的 handler push 到 _mutations[namespace] 里
entry.push(function wrappedMutationHandler(payload) {
// 传给 mutation handler 的两个参数,local.state 和 payload
handler.call(store, local.state, payload)
})
}
function registerAction(store, type, handler, local) {
const entry = store._actions[type] || (store._actions[type] = [])
// 将每个 namespace 对应的 handler push 到 _actions[namespace] 里
entry.push(function wrappedActionHandler(payload) {
// 传给 action handler 的两个参数
let res = handler.call(store, {
...local,
rootGetters: store.getters,
rootState: store.state
}, payload)
if(!isPromise(res)) {
res = Promise.resolve(res)
} else {
return res
}
})
}
installModule 的结果:
- 创建了 state 树(上面
new ModuleCollection只是创建了 module 树) - 注册了所有 module 的 getters、actions、mutations
其实这个时候,state、actions、mutations 已经可以用了,即 commit mutaions 和 dispatch actions 已经可以引起 state 的改变了,但也仅仅是改变而已。因为 state 还不是响应式,而且 store.getter 也还不可用,而这些工作则由 resetStoreVM 来做:
resetStoreVM
function resetStoreVM(store, state, hot) {
const oldVm = store._vm
store.getters = {}
store._makeLocalGettersCache = Object.create(null)
const wrapperedGetters = store._wrapperedGetters
const computed = {}
// fn 为 wrapperedGetters[key],前面 registerGetter 中
// fn 为一以 store 为参数,返回 rawGetter(...) 的函数
forEachValue(wrapperedGetters, (fn, key) => {
// 偏函数和 closure?
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get() {
return store._vm[key]
},
enumerable: true
})
})
const silent = Vue.config.silent
Vue.config.silent = true
// 通过将 state 传进 data.$$state,getters 传作 computed 使二者变为响应式
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent
// 开启 strict mode
if (store.strict) {
enableStrictMode(store);
}
// ... 其他在 new Vuex.store 时不会执行的代码
}
以上便是 new Vuex.store 过程中所有相关的代码。
这一过程执行完之后,我们大概就可以猜到 store.commit 发生作用的机制,肯定是通过 commit type 去 store._mutations 里面找对应的 handler 队列,然后遍历执行,执行的过程会丢到 store._withCommit(fn) 的 fn 里。
store.dispatch 也是一个机制,不过它可能要麻烦很多,因为要处理异步的问题。
而 mapState、mapGetters 那一干 API 则是一些锦上添花的。
我发现当某个 API 支持的参数类型有超过一种时,Vue、Vuex 等的处理方式都是先 noramlize 或者 unify 一下,也就是不管传的是什么,先通过一个函数转化成统一的格式,然后真正的 handler 只用针对这统一的格式做处理。比如上面的 mapState 应对不同的参数类型的数据、commit 不同参数类型的 mutation,还有 Vue 里面对不同 vnode 的 children 的 normalize。