当我们在代码中通过 import Vuex from 'vuex' 的时候,实际上引用的是一个对象,它的定义在 src/index.js 中:
import { Store, install } from './store'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
export default {
Store,//Store类,很多内置方法定义都在这个类里面
install,//这个方法主要是保证值安装一次vuex并且在每个组件中的beforeCreate中初始化了vuex,初始化就是保证store添加到了Vue中
version: '__VERSION__',
mapState,//mapState方法
mapMutations,//mapMutations方法
mapGetters,//mapGetters方法
mapActions,//mapActions方法
createNamespacedHelpers//一个方法,通过改变mapState等的命名空间(作用域)
}
可以看到,Vuex就是一个普通的对象,每个属性上面都做了注释。那我们平常用vuex时候一般都是这么做的:
new Vuex.Store({
actions,
getters,
state,
mutations,
modules
// ...
})
//然后就是把这个store实例化的对象后传给new Vue(..)
接下来就是看store初始化作了什么,store的定义在src/store.js中
export class Store {
import applyMixin from './mixin'
import devtoolPlugin from './plugins/devtool'
import ModuleCollection from './module/module-collection'
import { forEachValue, isObject, isPromise, assert, partial } from './util'
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
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
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.`)
}
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)//把模块关系树整理好
console.log('this._modules', this._modules)
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
// 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)
}
// 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)
// 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))
const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
if (useDevtools) {
devtoolPlugin(this)
}
}
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.`)
}
}
}
先看构造器中,除了this._module,其他都是简单的初始化,我们先看这个new ModuleCollection(options)这个实例主要是整理了下模块关系树,定义在src/module/module-collection.js中。
import Module from './module'
import { assert, forEachValue } from '../util'
export default class ModuleCollection {
constructor (rawRootModule) {
// register root module (Vuex.Store options)
this.register([], rawRootModule, false)
}
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)//this._children[key]
}, this.root)
}
getNamespace (path) {
let module = this.root
return path.reduce((namespace, key) => {
module = module.getChild(key)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
update (rawRootModule) {
update([], this.root, rawRootModule)
}
register (path, rawModule, runtime = true) {
if (process.env.NODE_ENV !== 'production') {
assertRawModule(path, rawModule)
}
console.log('path22', path)
const newModule = new Module(rawModule, runtime)
if (path.length === 0) {//如果长度是0那么就将module赋给root,也就是确定了根模块
this.root = newModule
} else {
const parent = this.get(path.slice(0, -1))//this._children[key]//也就是拿到了父模块
parent.addChild(path[path.length - 1], newModule)//parent._children[path[path.length - 1]] = newModule//就是给父模块和子模块确定了关系
}
console.log('rawModule.modules', rawModule.modules)
// register nested modules
if (rawModule.modules) {//外部传过来的模块
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)//每次递归的时候拿到是子模块的名,和子模块的体,比如a和{getters...},然后拿去注册
})
}
}
unregister (path) {
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
if (!parent.getChild(key).runtime) return
parent.removeChild(key)
}
isRegistered (path) {
const parent = this.get(path.slice(0, -1))
const key = path[path.length - 1]
return parent.hasChild(key)
}
}
可以看到这个ModelCollectiond的实例化就干了一件事
this.register([], rawRootModule, false)
再看register这个目录,结合上面代码中注释接着说:刚开始进去的时候,遇到什么跟生产环境有关的直接跳过,想想也知道和主要逻辑无关,接着毫无疑问,一个空数组,长度肯定为0,那么就作为了根模块了
if (path.length === 0) {//如果长度是0那么就将module赋给root,也就是确定了根模块
this.root = newModule
}
接着else肯定进去了,就是看
if (rawModule.modules) {//外部传过来的模块
forEachValue(rawModule.modules, (rawChildModule, key) => {
this.register(path.concat(key), rawChildModule, runtime)//每次递归的时候拿到是子模块的名,和子模块的体,比如a和{getters...},然后拿去注册
})
}
这断面代码的作用就是每次递归的时候拿到是子模块的名,和子模块的体,比如module a和它的内容{getters...},然后拿去注册,再走上面的流程一次,唯一不同的是这回可以进入上面的else分支了
} else {
const parent = this.get(path.slice(0, -1))//this._children[key]//也就是拿到了父模块
parent.addChild(path[path.length - 1], newModule)//parent._children[path[path.length - 1]] = newModule//就是给父模块和子模块确定了关系
}
get函数的定义:
get (path) {
return path.reduce((module, key) => {
return module.getChild(key)//this._children[key]
}, this.root)
}
这2段代码的作用就是确定下父子关系。至此所有的父子关系经过一套递归就完成了。那些什么unregister 和isRegistered 就是递归删属性和判定对象中否存在,随便看看就懂了。
继续回到store类中:src/store.js
接下来要开始安装模块了:
先判断是否有命名空间,如果有命名空间就把模块添加到_modulesNamespaceMap中,如果不是根模块就将模块名添加为响应式对象。
然后通过makeLocalContext获取本地的上下文环境,
其中makeLocalGetters缓存了各个模块的getters到store的_makeLocalGettersCache上,
getNestedState 获得了模块最终的state状态,dispatch和commit都是根据命名空间和path进行定义,如果没有命名空间那么就直接指向了root模块
接下就是根据指定好的上下文(store,local等)注册mutations,actions和getters.
接着就是实例化Store._vm(通过resetStoreVM(store, state, hot)实现)
接着看resetStoreVM
resetStoreVM 首先遍历了 _wrappedGetters 获得每个 getter 的函数 fn 和 key,然后定义了 computed[key] = () => fn(store)。
_wrappedGetters 的初始化过程在registerWrapper中找,这里 fn(store) 相当于执行如下方法:
store._wrappedGetters[type] = function wrappedGetter (store) {
return rawGetter(
local.state, // local state
local.getters, // local getters
store.state, // root state
store.getters // root getters
)
}
接着就是给store添加._vm了
store._vm = new Vue({
data: {
$$state: state
},
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.
computed[key] = partial(fn, store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
也就是说拿着key去访问store.getters是遵循以下路径访问:
Store.getters[key] => store._vm[key] => computed[key] =>raw(local.state...store.state)
而访问store.state在class store中已经定义了
get state () {
return this._vm._data.$$state
}
也就是store.state => store._vm._data.$$state(就是实例化之前的那个state)
这样也就建立了state和getter的依赖关系,同时如果state发生了变化,那么getter由于computed的原因会去重新计算。
总结:
1. Vuex是一个普通的对象。
2. store的实例化是通过new ModelCollection去整理模块之间的关系,同时根据store和构建的上下文去注册mutation,action,getters,
3. 他们中间实现的链路就是
Store.getters[key] => store._vm[key] => computed[key] =>raw(local.state...store.state)=>this._vm._data.$$state
这样就达到了2种效果: