四、store初始化vm
调用resetStoreVM函数,把当前的store作为第一个参数,根部module的state作为第二个参数传入。resetStoreVM函数首先定义一个getters为空对象,然后通过wrappedGetters变量,把之前定义的store._wrappedGetters函数数组做保留,然后定义一个computed为空对象,接着通过forEachValue遍历store._wrappedGetters数组,首先执行computed[key] = partial(fn, store),为computed对象添加对应的函数,当真正执行的时候,会把store实例做为参数传给wrappedGetter方法,这样当调用wrappedGetter的时候就可以拿到store实例,以此访问到store.state和store.getters。最后通过Object.defineProperty为store.getters添加这些key,并且定义他是可枚举的,访问的方式是store._vm[key]。那么store._vm是在哪里定义的,接着往下看,他执行了store._vm = new Vue,也就是说store._vm代表的是一个vm实例,在new Vue的时候,他定义data是一个对象,其中的$$data为state(此处的state是根部module的state),接着把之前定义的coumputed对象作为vm实例的computed。当我们访问store.state的时候会触发store的实例方法state,也就是 this._vm._data.$$state,也就是我们刚才定义的vm实例的$$state,这样我们的state和我们定义的getter就成为了响应式的数据。接着如果我们传入的strict是true,那么会执行enableStrictMode函数,并把store实例作为第一个参数。enableStrictMode函数通过store._vm.$watch监听了this._data.$$state,并且deep为true,sync也为true。监听$$state报错的时机是store._committing为false。store._committing的默认值为false,那么什么时候会变为true,当我们触发_withCommit函数的时候,会先把 this._committing = true置为true,等传入的fn函数执行之后,store._committing又会被还原为false。在之前分析到的installModule函数中,如果注册的是子module,会为state通过_withCommit进行 Vue.set(parentState, moduleName, module.state)也就是修改state的操作。也就是说state的修改一定需要通过_withCommit函数的处理,也就是_committing为true时修改,在开发模式下才不会报出警告,官方文档中描述:
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
那么在执行commit的时候,会去把commit函数的触发放在_withCommit函数中去执行,这样就保证了vuex文档中的描述(开发者模式下,如果我们直接修改state,由于_commit为false,则会报错)。resetStore函数的最后逻辑是,如果发现我们之前就定义过store._vm,那么他会把之前的vm的$$data置为null,然后去执行旧的vm的$destroy()进行销毁。
// src/store.js
function resetStoreVM (store, state, hot) {
const oldVm = store._vm
// bind store public getters
store.getters = {}
// reset local getters cache
store._makeLocalGettersCache = Object.create(null)
const wrappedGetters = store._wrappedGetters
const 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
})
})
// 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())
}
}
...
function enableStrictMode (store) {
store._vm.$watch(function () { return this._data.$$state }, () => {
if (__DEV__) {
...
}
}, { deep: true, sync: true })
}
export class Store {
...
_withCommit (fn) {
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
...
commit (_type, _payload, _options) {
...
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
...
}
}