Vuex初始化

store.js文件中,我们可以在实例化Store的时候传递一个options,如下:

import Vuex from 'vuex'

export default new Vuex.Store({
  state: {...},
  mutaions: {...},
  actions: {...},
  getters: {...},
  modules: {...}
})

Vuex中,Store类的代码路径为src/store.js,其构造函数精简代码如下:

// store.js
export class Store {
  constructor (options = {}) {
    // 省略代码
    if (__DEV__) {
      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.`)
    }
    // 省略代码
    this._modules = new ModuleCollection(options)
    // 省略代码
    const state = this._modules.root.state
    installModule(this, state, [], this._modules.root)
    resetStoreVM(this, state)
    // 省略代码
  }
}

在构造函数最开始,它在开发环境下进行了三个断言,分别是:在实例化Store之前必须安装Vuex必须支持Promise(Action需要)以及Store构造函数必须使用new关键词调用

在随后,它实例化了一个ModuleCollection,并把结果传递给了this._modules内部变量。由于ModuleCollection主要是用来处理modules父子关系的,因此我们会在随后的章节中详细进行介绍。

在讲解Vuex初始化时,我们以如下例子为例进行说明。

// store.js
export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state, count) {
      state.count = count
    }
  },
  actions: {
    increment ({ commit }, count) {
      commit('increment', count)
    }
  },
  getters: {
    storeCount (state) {
      return state.count
    }
  }
})

ModuleCollection类实例完毕后,其this._modules内部变量结果如下:

this._modules = {
  root: {
    namespaced: false,
    state: {
      count:
    },
    _children: {},
    rawModule: {...}
  }
}

随后,它会调用installModule方法来处理与StateMutationsActionsGetters以及Modules等相关的内容,其完整代码如下:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      Vue.set(parentState, moduleName, module.state)
    })
  }

  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)
  })

  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

State初始化

在介绍State初始化的时候,我们以如下代码为例:

// 模块A
const A = {
  namespaced: true,
  state: {
    count: 0
  }
}
// 模块B
const B = {
  namespaced: true,
  state: {
    count: 0
  }
}
// 主模块
new Vuex.Store({
  state: {
    count: 0
  },
  modules: {
    a: A,
    b: B
  }
})

当我们在Store构造函数中第一次调用installModule,我们给第二个参数传递的是一个空数组,根据这个参数我们能判断到底是主模块还是子模块。

installModule(this, state, [], this._modules.root)

installModule方法中与State相关代码如下:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  // 省略代码
  if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      Vue.set(parentState, moduleName, module.state)
    })
  }
  // 省略代码
}

在这个方法中,如果当前不是主模块,则会在主模块的state下定义其子模块:

const parentState = {
  count: 0
}
// 设置A模块的state
Vue.set(parentState, 'a', { count: 0 })
// 设置B模块的state
Vue.set(parentState, 'b', { count: 0 })

当子模块全部都处理完毕时,此时的store实例如下:

const store = {
  state: {
    count: 0,
    a: { count: 0 },
    b: { count: 0 }
  },
  ...
}

我们都知道,当在template模板中使用了Vuexstate数据后,当state数据发生变动时,会触发组件重新渲染。

其实关于State数据的响应式定义,它发生在resetStoreVM方法中:

let Vue
// 构造函数
resetStoreVM(this, state)

// resetStoreVM方法
function resetStoreVM (store, state, hot) {
  Vue.config.silent = true
  store._vm = new Vue({
    data: {
      $$state: state
    }
  })
}

Mutations初始化

在介绍Mutations的时候,我们以如下代码为例进行说明:

// store.js
// A模块
const A = {
  namespaced: true,
  state: {
    count: 0
  },
  mutations: {
    increment (state, count) {
      state.count = count
    }
  }
}
// B模块
const B = {
  namespaced: true,
  state: {
    count: 0
    },
    mutations: {
    increment (state, count) {
      state.count = count
    }
  }
}
// 主模块
export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state, count) {
      state.count = count
    }
  }
  modules: {
    a: A,
    b: B
  }
})

installModule方法中,关于Mutations相关代码如下:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)
  // 省略代码
  const local = module.context = makeLocalContext(store, namespace, path)
  module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })
}

在之前我们提到过new ModuleCollection,在ModuleCollection构造函数中,它会实例化一个Module类,在这个类中它定义了一系列原型方法,其中就有一个forEachMutation方法,如下:

export default class Module {
  constructor (rawModule, runtime) {...}
  forEachMutation (fn) {
    if (this._rawModule.mutations) {
      forEachValue(this._rawModule.mutations, fn)
    }
  }
}

其中forEachValue是一个工具方法,它是对forEach方法的一层封装,代码如下:

export function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

module.forEachMutation方法的主要作用就是遍历当前模块下所有的mutations,然后执行registerMutation

以我们的例子为例,第一次执行installModule方法时,它是主模块。此时的keyincrementnamespace为空字符串。

我们再来看registerMutation方法,代码如下:

function registerMutation (store, type, handler, local) {
  const entry = store._mutations[type] || (store._mutations[type] = [])
  entry.push(function wrappedMutationHandler (payload) {
    handler.call(store, local.state, payload)
  })
}

它所做的事情就是把我们key-value对象形式的mutations方法,处理成key-array形式,当第一次执行完registerMutation方法后,store._mutations代码表示如下:

// store
const store = {
  _mutations: {
    'increment': [function wrappedMutationHandler() { ... }]
  }
}

因为我们在A、B这两个子模块中都有定义mutations,我们先跳过是如何处理子模块的,这里只要知道会递归遍历子模块,然后再次执行installModule方法即可。

当第二次调用installModule方法时,此时是A子模块,在module.forEachMutation遍历时,此时的keyincrementnamespacea/

第二次执行完registerMutation方法后,store._mutations代码表示如下:

// store
const store = {
  _mutations: {
    'increment': [function wrappedMutationHandler() { ... }],
    'a/increment': [function wrappedMutationHandler () { ... }]
  }
}

对于模块B,它的处理过程和模块A是一样的,我们直接看最后的结果:

const store = {
  _mutations: {
    'increment': [function wrappedMutationHandler() { ... }],
    'a/increment': [function wrappedMutationHandler () { ... }],
    'b/increment': [function wrappedMutationHandler () { ... }]
  }
}

Actions初始化

Action初始化逻辑和Mutations的基本一致,只是多了一些细微的差别。

在介绍Actions初始化的过程中,我们以如下代码为例进行说明:

// store.js
// A模块
const A = {
  namespaced: true,
  state: {
    count: 0
  },
  mutations: {
    increment (state, count) {
      state.count = count
    }
  },
  actions: {
    increment ({ commit }, count) {
      commit('increment', count)
    }
  }
}
// B模块
const B = {
  namespaced: true,
  state: {
    count: 0
  },
  mutations: {
    increment (state, count) {
      state.count = count
    }
  },
  actions: {
    increment ({ commit }, count) {
      commit('increment', count)
    }
  }
}
// 主模块
export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state, count) {
      state.count = count
    }
  },
  actions: {
    increment ({ commit }, count) {
      commit('increment', count)
    }
  },
  modules: {
    a: A,
    b: B
  }
})

installModule方法中,关于Actions相关的代码如下:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)
  // 省略代码
  const local = module.context = makeLocalContext(store, namespace, path)
  module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })
  // 省略代码
}

其中forEachAction方法是在Module类中定义的原型方法,代码如下:

export default class Module {
  constructor (rawModule, runtime) { ... },
  forEachAction (fn) {
    if (this._rawModule.actions) {
      forEachValue(this._rawModule.actions, fn)
    }
  }

当第一次执行module.forEachAction的时候,此时是主模块。因为我们在主模块中也撰写了Actions,因此此时的keyincrementnamespace为空字符串。

我们再来看一下registerAction方法的相关代码,如下:

function registerAction (store, type, handler, local) {
  const entry = store._actions[type] || (store._actions[type] = [])
  entry.push(function wrappedActionHandler (payload) {
    let res = handler.call(store, {
      dispatch: local.dispatch,
      commit: local.commit,
      getters: local.getters,
      state: local.state,
      rootGetters: store.getters,
      rootState: store.state
    }, payload)
    if (!isPromise(res)) {
      res = Promise.resolve(res)
    }
    if (store._devtoolHook) {
      return res.catch(err => {
        store._devtoolHook.emit('vuex:error', err)
        throw err
      })
    } else {
      return res
    }
  })
}

我们可以看到,Actions的注册过程和Mutations的注册过程非常相似,只不过Actions函数返回的结果是基于Promise,而Mutations函数是直接返回结果。

也就是说,Actions是基于Promise来实现的,所以我们才能在Store构造函数中看到它对Promise进行了断言。

assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)

在注册完主模块的Actions后,store._actions用代码表示如下:

const store = {
  __actions: {
    'increment': [function wrappedActionHandler () {...}]
  }
}

子模块的Actions处理跟Mutations相同,会递归遍历子模块,然后执行重新执行installModule方法。

当第二次调用module.forEachAction方法时,此时的keyincrementnamespacea/,当执行完registerAction方法后,store._actions代码表示如下:

const store = {
  __actions: {
    'increment': [function wrappedActionHandler () {...}],
    'a/increment': [function wrappedActionHandler () { ... }]
  }
}

对于B模块的Actions,它和A模块的处理过程是相同的,我们直接看结果:

const store = {
  __actions: {
    'increment': [function wrappedActionHandler () {...}],
    'a/increment': [function wrappedActionHandler () { ... }],
    'b/increment': [function wrappedActionHandler () { ... }]
  }
}

Getters初始化和响应式

在介绍Getters初始化和响应式的时候,我们以如下代码为例进行说明:

// store.js
// A模块
const A = {
  namespaced: true,
  state: {
    count: 0
  },
  getters: {
    storeCount (state) {
      return state.count
    }
  }
}
// B模块
const B = {
  namespaced: true,
  state: {
    count: 0
  },
  getters: {
    storeCount (state) {
      return state.count
    }
  }
}
// 主模块
export default new Vuex.Store({
  state: {
    count: 0
  },
  modules: {
    a: A,
    b: B
  },
  getters: {
    storeCount (state) {
      return state.count
    }
  }
})

Getters初始化

installModule方法中关于Getters相关的代码如下:

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)
  // 省略代码
  const local = module.context = makeLocalContext(store, namespace, path)
  // 省略代码
  module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })
  // 省略代码
}

其中,module.forEachGetter是一个在Module类中定义的原型方法,如下:

export default class Module {
  constructor (rawModule, runtime) { ... },
  forEachGetter (fn) {
    if (this._rawModule.getters) {
      forEachValue(this._rawModule.getters, fn)
    }
  }

以我们的例子为例,当第一次遍历执行module.forEachGetter的时候,此时的keystoreCountnamespace为空字符串。

我们再来看一下registerGetter方法,其代码如下:

function registerGetter (store, type, rawGetter, local) {
  if (store._wrappedGetters[type]) {
    if (__DEV__) {
      console.error(`[vuex] duplicate getter key: ${type}`)
    }
    return
  }
  store._wrappedGetters[type] = function wrappedGetter (store) {
    return rawGetter(
      local.state, // local state
      local.getters, // local getters
      store.state, // root state
      store.getters // root getters
    )
  }
}

当第一次registerGetter执行完毕后,store.__wrappedGetters用代码表示如下:

const store = {
  __wrappedGetters: {
    'storeCount': function wrappedGetter () { ... }
  }
}

对于子模块的Getters而言,因为会递归遍历子模块然后再次调用installModule,所以当第二次执行module.forEachGetter的时候,keystoreCountnamespacea/

此时执行完registerGetter方法后,store.__wrappedGetters代码表示如下:

const store = {
  __wrappedGetters: {
    'storeCount': function wrappedGetter () { ... },
    'a/storeCount': function wrappedGetter () { ... }
  }
}

同样的道理,当最后一次执行完registerGetter方法后,store.__wrappedGetters代码表示如下:

const store = {
  __wrappedGetters: {
    'storeCount': function wrappedGetter () { ... },
    'a/storeCount': function wrappedGetter () { ... },
    'b/storeCount': function wrappedGetter () { ... }
  }
}

Getters响应式

Getters响应式的过程跟State类型,它发生在resetStoreVM方法中,相关代码如下:

function resetStoreVM (store, state, hot) {
  const oldVm = store._vm
  store.getters = {}
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computed = {}
  forEachValue(wrappedGetters, (fn, key) => {
    computed[key] = partial(fn, store)
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key],
      enumerable: true // for local getters
    })
  })
  store._vm = new Vue({
    computed
  })
}

根据以上代码我们可以发现,对于Getters的处理,它不光实现了响应式(Getters转换成Computed),它还对store.getters的访问进行了拦截。

例如:

// 组件中进行方法
this.$store.getters['storeCount']
this.$store.getters['a/storeCount']
this.$store.getters['b/storeCount']

// 相当于访问
this.$store._vm['storeCount']
this.$store._vm['a/storeCount']
this.$store._vm['b/storeCount']

Modules初始化

在介绍StateMutationsActions以及Getters的过程中,我们提到过如果遇到子模块,它会递归子模块,然后再次调用installModule方法。

function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)
  // 省略代码
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })

其中module.forEachChildModule类的一个原型方法,其代码如下:

export default class Module {
  constructor (rawModule, runtime) { ... },
  forEachChild (fn) {
    forEachValue(this._children, fn)
  }

我们可以看到在forEachChild方法中,它遍历的是this._children属性,那么this._children属性又是从哪里来的呢?

我们在回到Store构造函数中,它有如下代码:


export class Store {
  constructor (options = {}) {
    this._modules = new ModuleCollection(options)
  }
}

接着,我们再来看ModuleCollection类和Module类,其代码如下:

// module-collection.js
export default class ModuleCollection {
  constructor (rawRootModule) {
    this.register([], rawRootModule, false)
  }
  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      assertRawModule(path, rawModule)
    }
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
}
// module.js
export default class Module {
  constructor (rawModule, runtime) {
    this._children = Object.create(null)
  }
  addChild (key, module) {
    this._children[key] = module
  }
}

当第一次执行register的时候,主模块下面定义了modules,所以会递归调用register方法。

register方法中,它通过addChild来添加子模块,以我们前面的例子为例,当遍历完子模块以后,this._children代码表示如下:

// 演示需要,实际为Module实例
this._children = {
  a: 'Module',
  b: 'Module'
}

我们在回到forEachChild方法,在这个方法中它遍历的_children属性键,然后依次调用其中的fn方法。

module.forEachChild((child, key) => {
  installModule(store, rootState, path.concat(key), child, hot)
})

也就是说,当递归子模块的时候,会再次执行installModule方法,然后依次处理子模块的StateMutationsActions以及Getters等。

在以上步骤都执行完毕后,Store初始化操作基本也就结束了。在下一节中,我们来介绍Store辅助方法的设计思想。

最后更新时间:
贡献者: wangtunan