• Vuex 初始化
    • 安装
    • Store 实例化
      • 初始化模块
      • 安装模块
      • 初始化 store._vm
    • 总结

    Vuex 初始化

    这一节我们主要来分析 Vuex 的初始化过程,它包括安装、Store 实例化过程 2 个方面。

    安装

    当我们在代码中通过 import Vuex from 'vuex' 的时候,实际上引用的是一个对象,它的定义在 src/index.js 中:

    1. export default {
    2. Store,
    3. install,
    4. version: '__VERSION__',
    5. mapState,
    6. mapMutations,
    7. mapGetters,
    8. mapActions,
    9. createNamespacedHelpers
    10. }

    和 Vue-Router 一样,Vuex 也同样存在一个静态的 install 方法,它的定义在 src/store.js 中:

    1. export function install (_Vue) {
    2. if (Vue && _Vue === Vue) {
    3. if (process.env.NODE_ENV !== 'production') {
    4. console.error(
    5. '[vuex] already installed. Vue.use(Vuex) should be called only once.'
    6. )
    7. }
    8. return
    9. }
    10. Vue = _Vue
    11. applyMixin(Vue)
    12. }

    install 的逻辑很简单,把传入的 _Vue 赋值给 Vue 并执行了 applyMixin(Vue) 方法,它的定义在 src/mixin.js 中:

    1. export default function (Vue) {
    2. const version = Number(Vue.version.split('.')[0])
    3. if (version >= 2) {
    4. Vue.mixin({ beforeCreate: vuexInit })
    5. } else {
    6. // override init and inject vuex init procedure
    7. // for 1.x backwards compatibility.
    8. const _init = Vue.prototype._init
    9. Vue.prototype._init = function (options = {}) {
    10. options.init = options.init
    11. ? [vuexInit].concat(options.init)
    12. : vuexInit
    13. _init.call(this, options)
    14. }
    15. }
    16. /**
    17. * Vuex init hook, injected into each instances init hooks list.
    18. */
    19. function vuexInit () {
    20. const options = this.$options
    21. // store injection
    22. if (options.store) {
    23. this.$store = typeof options.store === 'function'
    24. ? options.store()
    25. : options.store
    26. } else if (options.parent && options.parent.$store) {
    27. this.$store = options.parent.$store
    28. }
    29. }
    30. }

    applyMixin 就是这个 export default function,它还兼容了 Vue 1.0 的版本,这里我们只关注 Vue 2.0 以上版本的逻辑,它其实就全局混入了一个 beforeCreate 钩子函数,它的实现非常简单,就是把 options.store 保存在所有组件的 this.$store 中,这个 options.store 就是我们在实例化 Store 对象的实例,稍后我们会介绍,这也是为什么我们在组件中可以通过 this.$store 访问到这个实例。

    Store 实例化

    我们在 import Vuex 之后,会实例化其中的 Store 对象,返回 store 实例并传入 new Vueoptions 中,也就是我们刚才提到的 options.store.

    举个简单的例子,如下:

    1. export default new Vuex.Store({
    2. actions,
    3. getters,
    4. state,
    5. mutations,
    6. modules
    7. // ...
    8. })

    Store 对象的构造函数接收一个对象参数,它包含 actionsgettersstatemutationsmodules 等 Vuex 的核心概念,它的定义在 src/store.js 中:

    1. export class Store {
    2. constructor (options = {}) {
    3. // Auto install if it is not done yet and `window` has `Vue`.
    4. // To allow users to avoid auto-installation in some cases,
    5. // this code should be placed here. See #731
    6. if (!Vue && typeof window !== 'undefined' && window.Vue) {
    7. install(window.Vue)
    8. }
    9. if (process.env.NODE_ENV !== 'production') {
    10. assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
    11. assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
    12. assert(this instanceof Store, `Store must be called with the new operator.`)
    13. }
    14. const {
    15. plugins = [],
    16. strict = false
    17. } = options
    18. // store internal state
    19. this._committing = false
    20. this._actions = Object.create(null)
    21. this._actionSubscribers = []
    22. this._mutations = Object.create(null)
    23. this._wrappedGetters = Object.create(null)
    24. this._modules = new ModuleCollection(options)
    25. this._modulesNamespaceMap = Object.create(null)
    26. this._subscribers = []
    27. this._watcherVM = new Vue()
    28. // bind commit and dispatch to self
    29. const store = this
    30. const { dispatch, commit } = this
    31. this.dispatch = function boundDispatch (type, payload) {
    32. return dispatch.call(store, type, payload)
    33. }
    34. this.commit = function boundCommit (type, payload, options) {
    35. return commit.call(store, type, payload, options)
    36. }
    37. // strict mode
    38. this.strict = strict
    39. const state = this._modules.root.state
    40. // init root module.
    41. // this also recursively registers all sub-modules
    42. // and collects all module getters inside this._wrappedGetters
    43. installModule(this, state, [], this._modules.root)
    44. // initialize the store vm, which is responsible for the reactivity
    45. // (also registers _wrappedGetters as computed properties)
    46. resetStoreVM(this, state)
    47. // apply plugins
    48. plugins.forEach(plugin => plugin(this))
    49. if (Vue.config.devtools) {
    50. devtoolPlugin(this)
    51. }
    52. }
    53. }

    我们把 Store 的实例化过程拆成 3 个部分,分别是初始化模块,安装模块和初始化 store._vm,接下来我们来分析这 3 部分的实现。

    初始化模块

    在分析模块初始化之前,我们先来了解一下模块对于 Vuex 的意义:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 statemutationactiongetter,甚至是嵌套子模块——从上至下进行同样方式的分割:

    1. const moduleA = {
    2. state: { ... },
    3. mutations: { ... },
    4. actions: { ... },
    5. getters: { ... }
    6. }
    7. const moduleB = {
    8. state: { ... },
    9. mutations: { ... },
    10. actions: { ... },
    11. getters: { ... },
    12. }
    13. const store = new Vuex.Store({
    14. modules: {
    15. a: moduleA,
    16. b: moduleB
    17. }
    18. })
    19. store.state.a // -> moduleA 的状态
    20. store.state.b // -> moduleB 的状态

    所以从数据结构上来看,模块的设计就是一个树型结构,store 本身可以理解为一个 root module,它下面的 modules 就是子模块,Vuex 需要完成这颗树的构建,构建过程的入口就是:

    1. this._modules = new ModuleCollection(options)

    ModuleCollection 的定义在 src/module/module-collection.js 中:

    1. export default class ModuleCollection {
    2. constructor (rawRootModule) {
    3. // register root module (Vuex.Store options)
    4. this.register([], rawRootModule, false)
    5. }
    6. get (path) {
    7. return path.reduce((module, key) => {
    8. return module.getChild(key)
    9. }, this.root)
    10. }
    11. getNamespace (path) {
    12. let module = this.root
    13. return path.reduce((namespace, key) => {
    14. module = module.getChild(key)
    15. return namespace + (module.namespaced ? key + '/' : '')
    16. }, '')
    17. }
    18. update (rawRootModule) {
    19. update([], this.root, rawRootModule)
    20. }
    21. register (path, rawModule, runtime = true) {
    22. if (process.env.NODE_ENV !== 'production') {
    23. assertRawModule(path, rawModule)
    24. }
    25. const newModule = new Module(rawModule, runtime)
    26. if (path.length === 0) {
    27. this.root = newModule
    28. } else {
    29. const parent = this.get(path.slice(0, -1))
    30. parent.addChild(path[path.length - 1], newModule)
    31. }
    32. // register nested modules
    33. if (rawModule.modules) {
    34. forEachValue(rawModule.modules, (rawChildModule, key) => {
    35. this.register(path.concat(key), rawChildModule, runtime)
    36. })
    37. }
    38. }
    39. unregister (path) {
    40. const parent = this.get(path.slice(0, -1))
    41. const key = path[path.length - 1]
    42. if (!parent.getChild(key).runtime) return
    43. parent.removeChild(key)
    44. }
    45. }

    ModuleCollection 实例化的过程就是执行了 register 方法,register 接收 3 个参数,其中 path 表示路径,因为我们整体目标是要构建一颗模块树,path 是在构建树的过程中维护的路径;rawModule 表示定义模块的原始配置;runtime 表示是否是一个运行时创建的模块。

    register 方法首先通过 const newModule = new Module(rawModule, runtime) 创建了一个 Module 的实例,Module 是用来描述单个模块的类,它的定义在 src/module/module.js 中:

    1. export default class Module {
    2. constructor (rawModule, runtime) {
    3. this.runtime = runtime
    4. // Store some children item
    5. this._children = Object.create(null)
    6. // Store the origin module object which passed by programmer
    7. this._rawModule = rawModule
    8. const rawState = rawModule.state
    9. // Store the origin module's state
    10. this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
    11. }
    12. get namespaced () {
    13. return !!this._rawModule.namespaced
    14. }
    15. addChild (key, module) {
    16. this._children[key] = module
    17. }
    18. removeChild (key) {
    19. delete this._children[key]
    20. }
    21. getChild (key) {
    22. return this._children[key]
    23. }
    24. update (rawModule) {
    25. this._rawModule.namespaced = rawModule.namespaced
    26. if (rawModule.actions) {
    27. this._rawModule.actions = rawModule.actions
    28. }
    29. if (rawModule.mutations) {
    30. this._rawModule.mutations = rawModule.mutations
    31. }
    32. if (rawModule.getters) {
    33. this._rawModule.getters = rawModule.getters
    34. }
    35. }
    36. forEachChild (fn) {
    37. forEachValue(this._children, fn)
    38. }
    39. forEachGetter (fn) {
    40. if (this._rawModule.getters) {
    41. forEachValue(this._rawModule.getters, fn)
    42. }
    43. }
    44. forEachAction (fn) {
    45. if (this._rawModule.actions) {
    46. forEachValue(this._rawModule.actions, fn)
    47. }
    48. }
    49. forEachMutation (fn) {
    50. if (this._rawModule.mutations) {
    51. forEachValue(this._rawModule.mutations, fn)
    52. }
    53. }
    54. }

    来看一下 Module 的构造函数,对于每个模块而言,this._rawModule 表示模块的配置,this._children 表示它的所有子模块,this.state 表示这个模块定义的 state

    回到 register,那么在实例化一个 Module 后,判断当前的 path 的长度如果为 0,则说明它是一个根模块,所以把 newModule 赋值给了 this.root,否则就需要建立父子关系了:

    1. const parent = this.get(path.slice(0, -1))
    2. parent.addChild(path[path.length - 1], newModule)

    我们先大体上了解它的逻辑:首先根据路径获取到父模块,然后再调用父模块的 addChild 方法建立父子关系。

    register 的最后一步,就是遍历当前模块定义中的所有 modules,根据 key 作为 path,递归调用 register 方法,这样我们再回过头看一下建立父子关系的逻辑,首先执行了 this.get(path.slice(0, -1) 方法:

    1. get (path) {
    2. return path.reduce((module, key) => {
    3. return module.getChild(key)
    4. }, this.root)
    5. }

    传入的 path 是它的父模块的 path,然后从根模块开始,通过 reduce 方法一层层去找到对应的模块,查找的过程中,执行的是 module.getChild(key) 方法:

    1. getChild (key) {
    2. return this._children[key]
    3. }

    其实就是返回当前模块的 _children 中对应 key 的模块,那么每个模块的 _children 是如何添加的呢,是通过执行 parent.addChild(path[path.length - 1], newModule) 方法:

    1. addChild (key, module) {
    2. this._children[key] = module
    3. }

    所以说对于 root module 的下一层 modules 来说,它们的 parent 就是 root module,那么他们就会被添加的 root module_children 中。每个子模块通过路径找到它的父模块,然后通过父模块的 addChild 方法建立父子关系,递归执行这样的过程,最终就建立一颗完整的模块树。

    安装模块

    初始化模块后,执行安装模块的相关逻辑,它的目标就是对模块中的 stategettersmutationsactions 做初始化工作,它的入口代码是:

    1. const state = this._modules.root.state
    2. installModule(this, state, [], this._modules.root)

    来看一下 installModule 的定义:

    1. function installModule (store, rootState, path, module, hot) {
    2. const isRoot = !path.length
    3. const namespace = store._modules.getNamespace(path)
    4. // register in namespace map
    5. if (module.namespaced) {
    6. store._modulesNamespaceMap[namespace] = module
    7. }
    8. // set state
    9. if (!isRoot && !hot) {
    10. const parentState = getNestedState(rootState, path.slice(0, -1))
    11. const moduleName = path[path.length - 1]
    12. store._withCommit(() => {
    13. Vue.set(parentState, moduleName, module.state)
    14. })
    15. }
    16. const local = module.context = makeLocalContext(store, namespace, path)
    17. module.forEachMutation((mutation, key) => {
    18. const namespacedType = namespace + key
    19. registerMutation(store, namespacedType, mutation, local)
    20. })
    21. module.forEachAction((action, key) => {
    22. const type = action.root ? key : namespace + key
    23. const handler = action.handler || action
    24. registerAction(store, type, handler, local)
    25. })
    26. module.forEachGetter((getter, key) => {
    27. const namespacedType = namespace + key
    28. registerGetter(store, namespacedType, getter, local)
    29. })
    30. module.forEachChild((child, key) => {
    31. installModule(store, rootState, path.concat(key), child, hot)
    32. })
    33. }

    installModule 方法支持 5 个参数,store 表示 root storestate 表示 root statepath 表示模块的访问路径;module 表示当前的模块,hot 表示是否是热更新。

    接下来看函数逻辑,这里涉及到了命名空间的概念,默认情况下,模块内部的 actionmutationgetter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction 作出响应。如果我们希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。例如:

    1. const store = new Vuex.Store({
    2. modules: {
    3. account: {
    4. namespaced: true,
    5. // 模块内容(module assets)
    6. state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
    7. getters: {
    8. isAdmin () { ... } // -> getters['account/isAdmin']
    9. },
    10. actions: {
    11. login () { ... } // -> dispatch('account/login')
    12. },
    13. mutations: {
    14. login () { ... } // -> commit('account/login')
    15. },
    16. // 嵌套模块
    17. modules: {
    18. // 继承父模块的命名空间
    19. myPage: {
    20. state: { ... },
    21. getters: {
    22. profile () { ... } // -> getters['account/profile']
    23. }
    24. },
    25. // 进一步嵌套命名空间
    26. posts: {
    27. namespaced: true,
    28. state: { ... },
    29. getters: {
    30. popular () { ... } // -> getters['account/posts/popular']
    31. }
    32. }
    33. }
    34. }
    35. }
    36. })

    回到 installModule 方法,我们首先根据 path 获取 namespace

    1. const namespace = store._modules.getNamespace(path)

    getNamespace 的定义在 src/module/module-collection.js 中:

    1. getNamespace (path) {
    2. let module = this.root
    3. return path.reduce((namespace, key) => {
    4. module = module.getChild(key)
    5. return namespace + (module.namespaced ? key + '/' : '')
    6. }, '')
    7. }

    root module 开始,通过 reduce 方法一层层找子模块,如果发现该模块配置了 namespaced 为 true,则把该模块的 key 拼到 namesapce 中,最终返回完整的 namespace 字符串。

    回到 installModule 方法,接下来把 namespace 对应的模块保存下来,为了方便以后能根据 namespace 查找模块:

    1. if (module.namespaced) {
    2. store._modulesNamespaceMap[namespace] = module
    3. }

    接下来判断非 root module 且非 hot 的情况执行一些逻辑,我们稍后再看。

    接着是很重要的逻辑,构造了一个本地上下文环境:

    1. const local = module.context = makeLocalContext(store, namespace, path)

    来看一下 makeLocalContext 实现:

    1. function makeLocalContext (store, namespace, path) {
    2. const noNamespace = namespace === ''
    3. const local = {
    4. dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
    5. const args = unifyObjectStyle(_type, _payload, _options)
    6. const { payload, options } = args
    7. let { type } = args
    8. if (!options || !options.root) {
    9. type = namespace + type
    10. if (process.env.NODE_ENV !== 'production' && !store._actions[type]) {
    11. console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
    12. return
    13. }
    14. }
    15. return store.dispatch(type, payload)
    16. },
    17. commit: noNamespace ? store.commit : (_type, _payload, _options) => {
    18. const args = unifyObjectStyle(_type, _payload, _options)
    19. const { payload, options } = args
    20. let { type } = args
    21. if (!options || !options.root) {
    22. type = namespace + type
    23. if (process.env.NODE_ENV !== 'production' && !store._mutations[type]) {
    24. console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
    25. return
    26. }
    27. }
    28. store.commit(type, payload, options)
    29. }
    30. }
    31. // getters and state object must be gotten lazily
    32. // because they will be changed by vm update
    33. Object.defineProperties(local, {
    34. getters: {
    35. get: noNamespace
    36. ? () => store.getters
    37. : () => makeLocalGetters(store, namespace)
    38. },
    39. state: {
    40. get: () => getNestedState(store.state, path)
    41. }
    42. })
    43. return local
    44. }

    makeLocalContext 支持 3 个参数相关,store 表示 root storenamespace 表示模块的命名空间,path 表示模块的 path

    该方法定义了 local 对象,对于 dispatchcommit 方法,如果没有 namespace,它们就直接指向了 root storedispatchcommit 方法,否则会创建方法,把 type 自动拼接上 namespace,然后执行 store 上对应的方法。

    对于 getters 而言,如果没有 namespace,则直接返回 root storegetters,否则返回 makeLocalGetters(store, namespace) 的返回值:

    1. function makeLocalGetters (store, namespace) {
    2. const gettersProxy = {}
    3. const splitPos = namespace.length
    4. Object.keys(store.getters).forEach(type => {
    5. // skip if the target getter is not match this namespace
    6. if (type.slice(0, splitPos) !== namespace) return
    7. // extract local getter type
    8. const localType = type.slice(splitPos)
    9. // Add a port to the getters proxy.
    10. // Define as getter property because
    11. // we do not want to evaluate the getters in this time.
    12. Object.defineProperty(gettersProxy, localType, {
    13. get: () => store.getters[type],
    14. enumerable: true
    15. })
    16. })
    17. return gettersProxy
    18. }

    makeLocalGetters 首先获取了 namespace 的长度,然后遍历 root store 下的所有 getters,先判断它的类型是否匹配 namespace,只有匹配的时候我们从 namespace 的位置截取后面的字符串得到 localType,接着用 Object.defineProperty 定义了 gettersProxy,获取 localType 实际上是访问了 store.getters[type]

    回到 makeLocalContext 方法,再来看一下对 state 的实现,它的获取则是通过 getNestedState(store.state, path) 方法:

    1. function getNestedState (state, path) {
    2. return path.length
    3. ? path.reduce((state, key) => state[key], state)
    4. : state
    5. }

    getNestedState 逻辑很简单,从 root state 开始,通过 path.reduce 方法一层层查找子模块 state,最终找到目标模块的 state

    那么构造完 local 上下文后,我们再回到 installModule 方法,接下来它就会遍历模块中定义的 mutationsactionsgetters,分别执行它们的注册工作,它们的注册逻辑都大同小异。

    • registerMutation
    1. module.forEachMutation((mutation, key) => {
    2. const namespacedType = namespace + key
    3. registerMutation(store, namespacedType, mutation, local)
    4. })
    5. function registerMutation (store, type, handler, local) {
    6. const entry = store._mutations[type] || (store._mutations[type] = [])
    7. entry.push(function wrappedMutationHandler (payload) {
    8. handler.call(store, local.state, payload)
    9. })
    10. }

    首先遍历模块中的 mutations 的定义,拿到每一个 mutationkey,并把 key 拼接上 namespace,然后执行 registerMutation 方法。该方法实际上就是给 root store 上的 _mutations[types] 添加 wrappedMutationHandler 方法,该方法的具体实现我们之后会提到。注意,同一 type_mutations 可以对应多个方法。

    • registerAction
    1. module.forEachAction((action, key) => {
    2. const type = action.root ? key : namespace + key
    3. const handler = action.handler || action
    4. registerAction(store, type, handler, local)
    5. })
    6. function registerAction (store, type, handler, local) {
    7. const entry = store._actions[type] || (store._actions[type] = [])
    8. entry.push(function wrappedActionHandler (payload, cb) {
    9. let res = handler.call(store, {
    10. dispatch: local.dispatch,
    11. commit: local.commit,
    12. getters: local.getters,
    13. state: local.state,
    14. rootGetters: store.getters,
    15. rootState: store.state
    16. }, payload, cb)
    17. if (!isPromise(res)) {
    18. res = Promise.resolve(res)
    19. }
    20. if (store._devtoolHook) {
    21. return res.catch(err => {
    22. store._devtoolHook.emit('vuex:error', err)
    23. throw err
    24. })
    25. } else {
    26. return res
    27. }
    28. })
    29. }

    首先遍历模块中的 actions 的定义,拿到每一个 actionkey,并判断 action.root,如果否的情况把 key 拼接上 namespace,然后执行 registerAction 方法。该方法实际上就是给 root store 上的 _actions[types] 添加 wrappedActionHandler 方法,该方法的具体实现我们之后会提到。注意,同一 type_actions 可以对应多个方法。

    • registerGetter
    1. module.forEachGetter((getter, key) => {
    2. const namespacedType = namespace + key
    3. registerGetter(store, namespacedType, getter, local)
    4. })
    5. function registerGetter (store, type, rawGetter, local) {
    6. if (store._wrappedGetters[type]) {
    7. if (process.env.NODE_ENV !== 'production') {
    8. console.error(`[vuex] duplicate getter key: ${type}`)
    9. }
    10. return
    11. }
    12. store._wrappedGetters[type] = function wrappedGetter (store) {
    13. return rawGetter(
    14. local.state, // local state
    15. local.getters, // local getters
    16. store.state, // root state
    17. store.getters // root getters
    18. )
    19. }
    20. }

    首先遍历模块中的 getters 的定义,拿到每一个 getterkey,并把 key 拼接上 namespace,然后执行 registerGetter 方法。该方法实际上就是给 root store 上的 _wrappedGetters[key] 指定 wrappedGetter 方法,该方法的具体实现我们之后会提到。注意,同一 type_wrappedGetters 只能定义一个。

    再回到 installModule 方法,最后一步就是遍历模块中的所有子 modules,递归执行 installModule 方法:

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

    之前我们忽略了非 root module 下的 state 初始化逻辑,现在来看一下:

    1. if (!isRoot && !hot) {
    2. const parentState = getNestedState(rootState, path.slice(0, -1))
    3. const moduleName = path[path.length - 1]
    4. store._withCommit(() => {
    5. Vue.set(parentState, moduleName, module.state)
    6. })
    7. }

    之前我们提到过 getNestedState 方法,它是从 root state 开始,一层层根据模块名能访问到对应 pathstate,那么它每一层关系的建立实际上就是通过这段 state 的初始化逻辑。store._withCommit 方法我们之后再介绍。

    所以 installModule 实际上就是完成了模块下的 stategettersactionsmutations 的初始化工作,并且通过递归遍历的方式,就完成了所有子模块的安装工作。

    初始化 store._vm

    Store 实例化的最后一步,就是执行初始化 store._vm 的逻辑,它的入口代码是:

    1. resetStoreVM(this, state)

    来看一下 resetStoreVM 的定义:

    1. function resetStoreVM (store, state, hot) {
    2. const oldVm = store._vm
    3. // bind store public getters
    4. store.getters = {}
    5. const wrappedGetters = store._wrappedGetters
    6. const computed = {}
    7. forEachValue(wrappedGetters, (fn, key) => {
    8. // use computed to leverage its lazy-caching mechanism
    9. computed[key] = () => fn(store)
    10. Object.defineProperty(store.getters, key, {
    11. get: () => store._vm[key],
    12. enumerable: true // for local getters
    13. })
    14. })
    15. // use a Vue instance to store the state tree
    16. // suppress warnings just in case the user has added
    17. // some funky global mixins
    18. const silent = Vue.config.silent
    19. Vue.config.silent = true
    20. store._vm = new Vue({
    21. data: {
    22. $$state: state
    23. },
    24. computed
    25. })
    26. Vue.config.silent = silent
    27. // enable strict mode for new vm
    28. if (store.strict) {
    29. enableStrictMode(store)
    30. }
    31. if (oldVm) {
    32. if (hot) {
    33. // dispatch changes in all subscribed watchers
    34. // to force getter re-evaluation for hot reloading.
    35. store._withCommit(() => {
    36. oldVm._data.$$state = null
    37. })
    38. }
    39. Vue.nextTick(() => oldVm.$destroy())
    40. }
    41. }

    resetStoreVM 的作用实际上是想建立 gettersstate 的联系,因为从设计上 getters 的获取就依赖了 state ,并且希望它的依赖能被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。因此这里利用了 Vue 中用 computed 计算属性来实现。

    resetStoreVM 首先遍历了 _wrappedGetters 获得每个 getter 的函数 fnkey,然后定义了 computed[key] = () => fn(store)。我们之前提到过 _wrappedGetters 的初始化过程,这里 fn(store) 相当于执行如下方法:

    1. store._wrappedGetters[type] = function wrappedGetter (store) {
    2. return rawGetter(
    3. local.state, // local state
    4. local.getters, // local getters
    5. store.state, // root state
    6. store.getters // root getters
    7. )
    8. }

    返回的就是 rawGetter 的执行函数,rawGetter 就是用户定义的 getter 函数,它的前 2 个参数是 local statelocal getters,后 2 个参数是 root stateroot getters

    接着实例化一个 Vue 实例 store._vm,并把 computed 传入:

    1. store._vm = new Vue({
    2. data: {
    3. $$state: state
    4. },
    5. computed
    6. })

    我们发现 data 选项里定义了 $$state 属性,而我们访问 store.state 的时候,实际上会访问 Store 类上定义的 stateget 方法:

    1. get state () {
    2. return this._vm._data.$$state
    3. }

    它实际上就访问了 store._vm._data.$$state。那么 gettersstate 是如何建立依赖逻辑的呢,我们再看这段代码逻辑:

    1. forEachValue(wrappedGetters, (fn, key) => {
    2. // use computed to leverage its lazy-caching mechanism
    3. computed[key] = () => fn(store)
    4. Object.defineProperty(store.getters, key, {
    5. get: () => store._vm[key],
    6. enumerable: true // for local getters
    7. })
    8. })

    当我根据 key 访问 store.getters 的某一个 getter 的时候,实际上就是访问了 store._vm[key],也就是 computed[key],在执行 computed[key] 对应的函数的时候,会执行 rawGetter(local.state,…) 方法,那么就会访问到 store.state,进而访问到 store._vm._data.$$state,这样就建立了一个依赖关系。当 store.state 发生变化的时候,下一次再访问 store.getters 的时候会重新计算。

    我们再来看一下 strict mode 的逻辑:

    1. if (store.strict) {
    2. enableStrictMode(store)
    3. }
    4. function enableStrictMode (store) {
    5. store._vm.$watch(function () { return this._data.$$state }, () => {
    6. if (process.env.NODE_ENV !== 'production') {
    7. assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
    8. }
    9. }, { deep: true, sync: true })
    10. }

    当严格模式下,store._vm 会添加一个 wathcer 来观测 this._data.$$state 的变化,也就是当 store.state 被修改的时候, store._committing 必须为 true,否则在开发阶段会报警告。store._committing 默认值是 false,那么它什么时候会 true 呢,Store 定义了 _withCommit 实例方法:

    1. _withCommit (fn) {
    2. const committing = this._committing
    3. this._committing = true
    4. fn()
    5. this._committing = committing
    6. }

    它就是对 fn 包装了一个环境,确保在 fn 中执行任何逻辑的时候 this._committing = true。所以外部任何非通过 Vuex 提供的接口直接操作修改 state 的行为都会在开发阶段触发警告。

    总结

    那么至此,Vuex 的初始化过程就分析完毕了,除了安装部分,我们重点分析了 Store 的实例化过程。我们要把 store 想象成一个数据仓库,为了更方便的管理仓库,我们把一个大的 store 拆成一些 modules,整个 modules 是一个树型结构。每个 module 又分别定义了 stategettersmutationsactions,我们也通过递归遍历模块的方式都完成了它们的初始化。为了 module 具有更高的封装度和复用性,还定义了 namespace 的概念。最后我们还定义了一个内部的 Vue 实例,用来建立 stategetters 的联系,并且可以在严格模式下监测 state 的变化是不是来自外部,确保改变 state 的唯一途径就是显式地提交 mutation

    这一节我们已经建立好 store,接下来就是对外提供了一些 API 方便我们对这个 store 做数据存取的操作,下一节我们就来从源码角度来分析 Vuex 提供的一系列 API。

    原文: https://ustbhuangyi.github.io/vue-analysis/vuex/init.html