分析 Vuex 的初始化过程,它包括安装、Store 实例化过程 2 个方面

安装

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

  1. import { Store, install } from './store'
  2. import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
  3. import createLogger from './plugins/logger'
  4. export default {
  5. Store,
  6. install,
  7. version: '__VERSION__',
  8. mapState,
  9. mapMutations,
  10. mapGetters,
  11. mapActions,
  12. createNamespacedHelpers,
  13. createLogger
  14. }

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

  1. export function install (_Vue) {
  2. if (Vue && _Vue === Vue) {
  3. if (__DEV__) {
  4. console.error(
  5. '[vuex] already installed. Vue.use(Vuex) should be called only once.'
  6. )
  7. }
  8. return
  9. }
  10. // 把传入的_Vue赋值给Vue并执行applyMixin(Vue)方法
  11. Vue = _Vue
  12. applyMixin(Vue)
  13. }

applyMixin定义在 src/mixin.js 中

  1. // applyMixin就是导出的匿名函数,兼容Vue1.x版本
  2. export default function (Vue) {
  3. const version = Number(Vue.version.split('.')[0])
  4. if (version >= 2) {
  5. // Vue2.x版本 全局混入一个beforeCreate钩子函数
  6. Vue.mixin({ beforeCreate: vuexInit })
  7. } else { // Vue1.x版本
  8. // override init and inject vuex init procedure
  9. // for 1.x backwards compatibility.
  10. const _init = Vue.prototype._init
  11. Vue.prototype._init = function (options = {}) {
  12. options.init = options.init
  13. ? [vuexInit].concat(options.init)
  14. : vuexInit
  15. _init.call(this, options)
  16. }
  17. }
  18. /**
  19. * Vuex init hook, injected into each instances init hooks list.
  20. */
  21. function vuexInit () {
  22. const options = this.$options
  23. // store injection
  24. if (options.store) {
  25. // 把options.store保存在所有组件的this.$store中 options.store就是在实例化Store对象的实例,也是为什么在组件中可以通过this.$store访问到这个实例
  26. this.$store = typeof options.store === 'function'
  27. ? options.store()
  28. : options.store
  29. } else if (options.parent && options.parent.$store) {
  30. this.$store = options.parent.$store
  31. }
  32. }
  33. }

Store 实例化

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

例子

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

Store 对象的构造函数接收一个对象参数,它包含 actions、getters、state、mutations、modules 等 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 (__DEV__) {
  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. this._makeLocalGettersCache = Object.create(null)
  29. // bind commit and dispatch to self
  30. const store = this
  31. const { dispatch, commit } = this
  32. this.dispatch = function boundDispatch (type, payload) {
  33. return dispatch.call(store, type, payload)
  34. }
  35. this.commit = function boundCommit (type, payload, options) {
  36. return commit.call(store, type, payload, options)
  37. }
  38. // strict mode
  39. this.strict = strict
  40. const state = this._modules.root.state
  41. // init root module.
  42. // this also recursively registers all sub-modules
  43. // and collects all module getters inside this._wrappedGetters
  44. installModule(this, state, [], this._modules.root)
  45. // initialize the store vm, which is responsible for the reactivity
  46. // (also registers _wrappedGetters as computed properties)
  47. resetStoreVM(this, state)
  48. // apply plugins
  49. plugins.forEach(plugin => plugin(this))
  50. const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
  51. if (useDevtools) {
  52. devtoolPlugin(this)
  53. }
  54. }
  55. get state () {
  56. return this._vm._data.$$state
  57. }
  58. set state (v) {
  59. if (__DEV__) {
  60. assert(false, `use store.replaceState() to explicit replace store state.`)
  61. }
  62. }
  63. commit (_type, _payload, _options) {
  64. // check object-style commit
  65. const {
  66. type,
  67. payload,
  68. options
  69. } = unifyObjectStyle(_type, _payload, _options)
  70. const mutation = { type, payload }
  71. const entry = this._mutations[type]
  72. if (!entry) {
  73. if (__DEV__) {
  74. console.error(`[vuex] unknown mutation type: ${type}`)
  75. }
  76. return
  77. }
  78. this._withCommit(() => {
  79. entry.forEach(function commitIterator (handler) {
  80. handler(payload)
  81. })
  82. })
  83. this._subscribers
  84. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  85. .forEach(sub => sub(mutation, this.state))
  86. if (
  87. __DEV__ &&
  88. options && options.silent
  89. ) {
  90. console.warn(
  91. `[vuex] mutation type: ${type}. Silent option has been removed. ` +
  92. 'Use the filter functionality in the vue-devtools'
  93. )
  94. }
  95. }
  96. dispatch (_type, _payload) {
  97. // check object-style dispatch
  98. const {
  99. type,
  100. payload
  101. } = unifyObjectStyle(_type, _payload)
  102. const action = { type, payload }
  103. const entry = this._actions[type]
  104. if (!entry) {
  105. if (__DEV__) {
  106. console.error(`[vuex] unknown action type: ${type}`)
  107. }
  108. return
  109. }
  110. try {
  111. this._actionSubscribers
  112. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  113. .filter(sub => sub.before)
  114. .forEach(sub => sub.before(action, this.state))
  115. } catch (e) {
  116. if (__DEV__) {
  117. console.warn(`[vuex] error in before action subscribers: `)
  118. console.error(e)
  119. }
  120. }
  121. const result = entry.length > 1
  122. ? Promise.all(entry.map(handler => handler(payload)))
  123. : entry[0](payload)
  124. return new Promise((resolve, reject) => {
  125. result.then(res => {
  126. try {
  127. this._actionSubscribers
  128. .filter(sub => sub.after)
  129. .forEach(sub => sub.after(action, this.state))
  130. } catch (e) {
  131. if (__DEV__) {
  132. console.warn(`[vuex] error in after action subscribers: `)
  133. console.error(e)
  134. }
  135. }
  136. resolve(res)
  137. }, error => {
  138. try {
  139. this._actionSubscribers
  140. .filter(sub => sub.error)
  141. .forEach(sub => sub.error(action, this.state, error))
  142. } catch (e) {
  143. if (__DEV__) {
  144. console.warn(`[vuex] error in error action subscribers: `)
  145. console.error(e)
  146. }
  147. }
  148. reject(error)
  149. })
  150. })
  151. }
  152. subscribe (fn, options) {
  153. return genericSubscribe(fn, this._subscribers, options)
  154. }
  155. subscribeAction (fn, options) {
  156. const subs = typeof fn === 'function' ? { before: fn } : fn
  157. return genericSubscribe(subs, this._actionSubscribers, options)
  158. }
  159. watch (getter, cb, options) {
  160. if (__DEV__) {
  161. assert(typeof getter === 'function', `store.watch only accepts a function.`)
  162. }
  163. return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  164. }
  165. replaceState (state) {
  166. this._withCommit(() => {
  167. this._vm._data.$$state = state
  168. })
  169. }
  170. registerModule (path, rawModule, options = {}) {
  171. if (typeof path === 'string') path = [path]
  172. if (__DEV__) {
  173. assert(Array.isArray(path), `module path must be a string or an Array.`)
  174. assert(path.length > 0, 'cannot register the root module by using registerModule.')
  175. }
  176. this._modules.register(path, rawModule)
  177. installModule(this, this.state, path, this._modules.get(path), options.preserveState)
  178. // reset store to update getters...
  179. resetStoreVM(this, this.state)
  180. }
  181. unregisterModule (path) {
  182. if (typeof path === 'string') path = [path]
  183. if (__DEV__) {
  184. assert(Array.isArray(path), `module path must be a string or an Array.`)
  185. }
  186. this._modules.unregister(path)
  187. this._withCommit(() => {
  188. const parentState = getNestedState(this.state, path.slice(0, -1))
  189. Vue.delete(parentState, path[path.length - 1])
  190. })
  191. resetStore(this)
  192. }
  193. hasModule (path) {
  194. if (typeof path === 'string') path = [path]
  195. if (__DEV__) {
  196. assert(Array.isArray(path), `module path must be a string or an Array.`)
  197. }
  198. return this._modules.isRegistered(path)
  199. }
  200. hotUpdate (newOptions) {
  201. this._modules.update(newOptions)
  202. resetStore(this, true)
  203. }
  204. _withCommit (fn) {
  205. const committing = this._committing
  206. this._committing = true
  207. fn()
  208. this._committing = committing
  209. }
  210. }

把 Store 的实例化过程拆成 3 个部分,分别是初始化模块,安装模块和初始化 store._vm

初始化模块

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

  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. // ModuleCollection实例化的过程就是执行了register方法,三个参数
  5. // path 表示路径,因为整体目标是要构建一颗模块树,path是在构建树的过程中维护的路径
  6. // rawModule 表示定义模块的原始配置
  7. // runtime 表示是否是一个运行时创建的模块
  8. this.register([], rawRootModule, false)
  9. }
  10. // 传入的path是它父模块的path
  11. get (path) {
  12. // 从根模块开始,通过reduce方法一层层去找到对应的模块
  13. // 查找的过程中执行的是module.getChild(key)方法
  14. return path.reduce((module, key) => {
  15. return module.getChild(key)
  16. }, this.root)
  17. }
  18. getNamespace (path) {
  19. let module = this.root
  20. return path.reduce((namespace, key) => {
  21. module = module.getChild(key)
  22. return namespace + (module.namespaced ? key + '/' : '')
  23. }, '')
  24. }
  25. update (rawRootModule) {
  26. update([], this.root, rawRootModule)
  27. }
  28. register (path, rawModule, runtime = true) {
  29. if (__DEV__) {
  30. assertRawModule(path, rawModule)
  31. }
  32. // 创建一个Module的实例- Module是用来描述单个模块的库
  33. const newModule = new Module(rawModule, runtime)
  34. if (path.length === 0) { // 根模块
  35. this.root = newModule
  36. } else {
  37. // 建立父子关系
  38. // 根据路径获取到父模块
  39. const parent = this.get(path.slice(0, -1))
  40. // 再调用父模块的addChild方法创建父子关系
  41. parent.addChild(path[path.length - 1], newModule)
  42. }
  43. // register nested modules
  44. if (rawModule.modules) {
  45. // 遍历当前模块定义中的所有modules,根据key作为path,递归调用register方法
  46. forEachValue(rawModule.modules, (rawChildModule, key) => {
  47. this.register(path.concat(key), rawChildModule, runtime)
  48. })
  49. }
  50. }
  51. unregister (path) {
  52. const parent = this.get(path.slice(0, -1))
  53. const key = path[path.length - 1]
  54. const child = parent.getChild(key)
  55. if (!child) {
  56. if (__DEV__) {
  57. console.warn(
  58. `[vuex] trying to unregister module '${key}', which is ` +
  59. `not registered`
  60. )
  61. }
  62. return
  63. }
  64. if (!child.runtime) {
  65. return
  66. }
  67. parent.removeChild(key)
  68. }
  69. isRegistered (path) {
  70. const parent = this.get(path.slice(0, -1))
  71. const key = path[path.length - 1]
  72. if (parent) {
  73. return parent.hasChild(key)
  74. }
  75. return false
  76. }
  77. }

Module是用来描述单个模块的库,它的定义在 src/module/module.js 中

  1. // Base data struct for store's module, package with some attribute and method
  2. export default class Module {
  3. constructor (rawModule, runtime) {
  4. this.runtime = runtime
  5. // Store some children item
  6. // 所有子模块
  7. this._children = Object.create(null)
  8. // Store the origin module object which passed by programmer
  9. // 模块的配置
  10. this._rawModule = rawModule
  11. const rawState = rawModule.state
  12. // Store the origin module's state
  13. // 该模块定义的state
  14. this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  15. }
  16. get namespaced () {
  17. return !!this._rawModule.namespaced
  18. }
  19. addChild (key, module) {
  20. this._children[key] = module
  21. }
  22. removeChild (key) {
  23. delete this._children[key]
  24. }
  25. getChild (key) {
  26. // 返回当前模块的_children中对应key的模块
  27. return this._children[key]
  28. }
  29. hasChild (key) {
  30. return key in this._children
  31. }
  32. update (rawModule) {
  33. this._rawModule.namespaced = rawModule.namespaced
  34. if (rawModule.actions) {
  35. this._rawModule.actions = rawModule.actions
  36. }
  37. if (rawModule.mutations) {
  38. this._rawModule.mutations = rawModule.mutations
  39. }
  40. if (rawModule.getters) {
  41. this._rawModule.getters = rawModule.getters
  42. }
  43. }
  44. forEachChild (fn) {
  45. forEachValue(this._children, fn)
  46. }
  47. forEachGetter (fn) {
  48. if (this._rawModule.getters) {
  49. forEachValue(this._rawModule.getters, fn)
  50. }
  51. }
  52. forEachAction (fn) {
  53. if (this._rawModule.actions) {
  54. forEachValue(this._rawModule.actions, fn)
  55. }
  56. }
  57. forEachMutation (fn) {
  58. if (this._rawModule.mutations) {
  59. forEachValue(this._rawModule.mutations, fn)
  60. }
  61. }
  62. }

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

安装模块

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

  1. const state = this._modules.root.state
  2. // init root module.
  3. // this also recursively registers all sub-modules
  4. // and collects all module getters inside this._wrappedGetters
  5. installModule(this, state, [], this._modules.root)

installModule的定义

  1. // store表示root store
  2. // state表示root state
  3. // path表示模块的访问路径
  4. // module表示当前的模块
  5. // hot表示是否是热更新
  6. function installModule (store, rootState, path, module, hot) {
  7. const isRoot = !path.length
  8. // 根据path获取namespace
  9. const namespace = store._modules.getNamespace(path)
  10. // register in namespace map
  11. if (module.namespaced) {
  12. if (store._modulesNamespaceMap[namespace] && __DEV__) {
  13. console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
  14. }
  15. // 把namespace对应的模块保存下来,方便以后能根据namespace查找模块
  16. store._modulesNamespaceMap[namespace] = module
  17. }
  18. // set state
  19. // 非root module下的state初始化逻辑
  20. if (!isRoot && !hot) {
  21. // getNestedState方法是从root state开始,一层层根据模块名能访问到对应path的state,那么它每一层关系的建立实际上就是通过这段state的初始化逻辑
  22. const parentState = getNestedState(rootState, path.slice(0, -1))
  23. const moduleName = path[path.length - 1]
  24. store._withCommit(() => {
  25. if (__DEV__) {
  26. if (moduleName in parentState) {
  27. console.warn(
  28. `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
  29. )
  30. }
  31. }
  32. Vue.set(parentState, moduleName, module.state)
  33. })
  34. }
  35. // 构建一个本地上下文环境
  36. const local = module.context = makeLocalContext(store, namespace, path)
  37. // 遍历模块中定义的mutation、action、getter,分别执行注册工作
  38. // 遍历模块中的mutations的定义,拿到每一个mutation和 key,并把key拼接上 namespace,然后执行registerMutation方法
  39. module.forEachMutation((mutation, key) => {
  40. const namespacedType = namespace + key
  41. registerMutation(store, namespacedType, mutation, local)
  42. })
  43. // 遍历模块中的actions的定义,拿到每一个action和key,并判断action.root,如果否的情况把key拼接上namespace,然后执行registerAction方法
  44. module.forEachAction((action, key) => {
  45. const type = action.root ? key : namespace + key
  46. const handler = action.handler || action
  47. registerAction(store, type, handler, local)
  48. })
  49. // 遍历模块中的getters的定义,拿到每一个getter和key,并把key拼接上namespace,然后执行registerGetter方法
  50. module.forEachGetter((getter, key) => {
  51. const namespacedType = namespace + key
  52. registerGetter(store, namespacedType, getter, local)
  53. })
  54. // 遍历模块中的所有子modules,递归执行installModule方法
  55. module.forEachChild((child, key) => {
  56. installModule(store, rootState, path.concat(key), child, hot)
  57. })
  58. }

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应
如果希望模块具有更高的封装度和复用性,可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
例如

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

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

  1. getNamespace (path) {
  2. // 从root module开始通过reduce方法一层层找子模块,如果发现该模块配置了namespaced为true,则把该模块的key拼到namespace中,最终返回完整的namespace字符串
  3. let module = this.root
  4. return path.reduce((namespace, key) => {
  5. module = module.getChild(key)
  6. return namespace + (module.namespaced ? key + '/' : '')
  7. }, '')
  8. }

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

  1. // store表示root store
  2. // namespace表示模块的命名空间
  3. // path表示模块的path
  4. function makeLocalContext (store, namespace, path) {
  5. const noNamespace = namespace === ''
  6. const local = {
  7. dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
  8. const args = unifyObjectStyle(_type, _payload, _options)
  9. const { payload, options } = args
  10. let { type } = args
  11. if (!options || !options.root) {
  12. type = namespace + type
  13. if (__DEV__ && !store._actions[type]) {
  14. console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
  15. return
  16. }
  17. }
  18. return store.dispatch(type, payload)
  19. },
  20. commit: noNamespace ? store.commit : (_type, _payload, _options) => {
  21. const args = unifyObjectStyle(_type, _payload, _options)
  22. const { payload, options } = args
  23. let { type } = args
  24. if (!options || !options.root) {
  25. type = namespace + type
  26. if (__DEV__ && !store._mutations[type]) {
  27. console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
  28. return
  29. }
  30. }
  31. store.commit(type, payload, options)
  32. }
  33. }
  34. // getters and state object must be gotten lazily
  35. // because they will be changed by vm update
  36. Object.defineProperties(local, {
  37. // getters 如果没有namespace直接返回root store的getters,否则返回makeLocalGetters方法的返回值
  38. getters: {
  39. get: noNamespace
  40. ? () => store.getters
  41. : () => makeLocalGetters(store, namespace)
  42. },
  43. // states 获取是通过getNestedState方法
  44. state: {
  45. get: () => getNestedState(store.state, path)
  46. }
  47. })
  48. return local
  49. }

makeLocalGetters

  1. function makeLocalGetters (store, namespace) {
  2. if (!store._makeLocalGettersCache[namespace]) {
  3. const gettersProxy = {}
  4. // 获取了namespace的长度
  5. const splitPos = namespace.length
  6. // 遍历root store下所有的getters
  7. Object.keys(store.getters).forEach(type => {
  8. // skip if the target getter is not match this namespace
  9. // 先判断它的类型的是否匹配到namespace
  10. if (type.slice(0, splitPos) !== namespace) return
  11. // extract local getter type
  12. // 匹配到后从namespace的位置截取后面的字符串得到localType
  13. const localType = type.slice(splitPos)
  14. // Add a port to the getters proxy.
  15. // Define as getter property because
  16. // we do not want to evaluate the getters in this time.
  17. // 定义了gettersProxy,获取localType实际上是访问了store.getters[type]
  18. Object.defineProperty(gettersProxy, localType, {
  19. get: () => store.getters[type],
  20. enumerable: true
  21. })
  22. })
  23. store._makeLocalGettersCache[namespace] = gettersProxy
  24. }
  25. return store._makeLocalGettersCache[namespace]
  26. }

getNestedState

  1. function getNestedState (state, path) {
  2. // 从root state开始通过path.reduce方法一层层查找子模块state,最终找到目标模块的state
  3. return path.reduce((state, key) => state[key], state)
  4. }

registerMutation
同一type的_mutations可以对应多个方法

  1. // 给root store上的_mutations[types]添加wrappedMutationHandler方法
  2. function registerMutation (store, type, handler, local) {
  3. const entry = store._mutations[type] || (store._mutations[type] = [])
  4. entry.push(function wrappedMutationHandler (payload) {
  5. handler.call(store, local.state, payload)
  6. })
  7. }

registerAction
同一type的_actions可以对应多个方法

  1. // 给root store上的_actions[types]添加wrappedActionHandler方法
  2. function registerAction (store, type, handler, local) {
  3. const entry = store._actions[type] || (store._actions[type] = [])
  4. entry.push(function wrappedActionHandler (payload) {
  5. let res = handler.call(store, {
  6. dispatch: local.dispatch,
  7. commit: local.commit,
  8. getters: local.getters,
  9. state: local.state,
  10. rootGetters: store.getters,
  11. rootState: store.state
  12. }, payload)
  13. if (!isPromise(res)) {
  14. res = Promise.resolve(res)
  15. }
  16. if (store._devtoolHook) {
  17. return res.catch(err => {
  18. store._devtoolHook.emit('vuex:error', err)
  19. throw err
  20. })
  21. } else {
  22. return res
  23. }
  24. })
  25. }

registerGetter
同一 type 的_wrappedGetters只能定义一个

  1. // root store上的_wrappedGetters[key]指定wrappedGetter方法
  2. function registerGetter (store, type, rawGetter, local) {
  3. if (store._wrappedGetters[type]) {
  4. if (__DEV__) {
  5. console.error(`[vuex] duplicate getter key: ${type}`)
  6. }
  7. return
  8. }
  9. store._wrappedGetters[type] = function wrappedGetter (store) {
  10. return rawGetter(
  11. local.state, // local state
  12. local.getters, // local getters
  13. store.state, // root state
  14. store.getters // root getters
  15. )
  16. }
  17. }

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

初始化 store._vm

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

  1. // initialize the store vm, which is responsible for the reactivity
  2. // (also registers _wrappedGetters as computed properties)
  3. resetStoreVM(this, state)

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

  1. function resetStoreVM (store, state, hot) {
  2. const oldVm = store._vm
  3. // bind store public getters
  4. store.getters = {}
  5. // reset local getters cache
  6. store._makeLocalGettersCache = Object.create(null)
  7. const wrappedGetters = store._wrappedGetters
  8. const computed = {}
  9. // 遍历了wrappedGetters获得每个getter的函数fn和key
  10. forEachValue(wrappedGetters, (fn, key) => {
  11. // use computed to leverage its lazy-caching mechanism
  12. // direct inline function use will lead to closure preserving oldVm.
  13. // using partial to return function with only arguments preserved in closure environment.
  14. // partial(fn, store)使用匿名函数去执行fn(store)
  15. computed[key] = partial(fn, store)
  16. Object.defineProperty(store.getters, key, {
  17. get: () => store._vm[key],
  18. enumerable: true // for local getters
  19. })
  20. })
  21. // use a Vue instance to store the state tree
  22. // suppress warnings just in case the user has added
  23. // some funky global mixins
  24. const silent = Vue.config.silent
  25. Vue.config.silent = true
  26. // 实例化一个Vue实例store._vm,并把computed传入
  27. store._vm = new Vue({
  28. data: {
  29. $$state: state // 访问store.state时实际上会访问Store类上定义的state的get方法
  30. },
  31. computed
  32. })
  33. Vue.config.silent = silent
  34. // enable strict mode for new vm
  35. // 严格模式下
  36. if (store.strict) {
  37. enableStrictMode(store)
  38. }
  39. if (oldVm) {
  40. if (hot) {
  41. // dispatch changes in all subscribed watchers
  42. // to force getter re-evaluation for hot reloading.
  43. store._withCommit(() => {
  44. oldVm._data.$$state = null
  45. })
  46. }
  47. Vue.nextTick(() => oldVm.$destroy())
  48. }
  49. }

partial

  1. export function partial (fn, arg) {
  2. return function () {
  3. return fn(arg)
  4. }
  5. }
  6. // fn(store)相当于执行
  7. store._wrappedGetters[type] = function wrappedGetter (store) {
  8. return rawGetter(
  9. local.state, // local state
  10. local.getters, // local getters
  11. store.state, // root state
  12. store.getters // root getters
  13. )
  14. }

返回的就是 rawGetter 的执行函数,rawGetter 就是用户定义的 getter 函数
Store类上定义的state的get方法

  1. get state () {
  2. return this._vm._data.$$state // 实际上访问了store._vm._data.$$state
  3. }

getters 和 state 是如何建立依赖逻辑的呢

  1. forEachValue(wrappedGetters, (fn, key) => {
  2. // use computed to leverage its lazy-caching mechanism
  3. // direct inline function use will lead to closure preserving oldVm.
  4. // using partial to return function with only arguments preserved in closure environment.
  5. computed[key] = partial(fn, store)
  6. Object.defineProperty(store.getters, key, {
  7. get: () => store._vm[key],
  8. enumerable: true // for local getters
  9. })
  10. })

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

  1. function enableStrictMode (store) {
  2. store._vm.$watch(function () { return this._data.$$state }, () => {
  3. if (__DEV__) {
  4. assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
  5. }
  6. }, { deep: true, sync: true })
  7. }

当严格模式下,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 又分别定义了 state,getters,mutations、actions,通过递归遍历模块的方式都完成了它们的初始化
为了 module 具有更高的封装度和复用性,还定义了 namespace 的概念
最后还定义了一个内部的 Vue 实例,用来建立 state 到 getters 的联系,并且可以在严格模式下监测 state 的变化是不是来自外部,确保改变 state 的唯一途径就是显式地提交 mutation