vuex源码分析(四)

726 阅读1分钟

四、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.statestore.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)
      })
    })
   ...
  }
}