分析开始

以官网的例子,来分析vuex的源码,首先从引入开始。

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. Vue.use(Vuex)
  4. const store = new Vuex.Store({
  5. state: {
  6. count: 0
  7. },
  8. mutations: {
  9. increment (state) {
  10. state.count++
  11. }
  12. }
  13. })

以上用的是标准的Vue 插件模式,所以很容易推测Vuex会有一个install的值,下面就是对vuex源码的具体解析,vuex源码地址 链接

vuex/src/index.js 文件如下,他导出了我们熟悉的Store, install, mapState, mapGetters, mapMutations, mapActions

  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. }
  15. export {
  16. Store,
  17. install,
  18. mapState,
  19. mapMutations,
  20. mapGetters,
  21. mapActions,
  22. createNamespacedHelpers,
  23. createLogger
  24. }

install

我们先从导出的install函数看起(下面提到的Vue只是一个变量,不是我们引入的vue)

vue/src/store.js 该文件重要的只有两句话(直接忽略掉__DEV相关的判断,看主干内容)

  1. (14)将传入的_vue赋值给 Vue 参数
  2. (15)applyMixin(Vue) 给Vue Mixin一些内容(继续后面内容) ```javascript let Vue

// … … 展示省略中间的很多内容,只保留当前相关的内容

export function install (Vue) { if (Vue && Vue === Vue) { if (__DEV) { console.error( ‘[vuex] already installed. Vue.use(Vuex) should be called only once.’ ) } return } Vue = _Vue applyMixin(Vue) }

  1. **vuex/src/mixins.js**(上面提到的applyMixin函数)下面代码可以看出,vuex针对不同的vue版本进行了处理:
  2. - vue的版本≥2时(4-5),是通过mixin混入beforeCreate钩子,将vuex初始化进来的
  3. - vue的版本<2时(9-14),是透过Vue的原型链,将vuex初始化进来的
  4. ```javascript
  5. export default function (Vue) {
  6. const version = Number(Vue.version.split('.')[0])
  7. if (version >= 2) {
  8. Vue.mixin({ beforeCreate: vuexInit })
  9. } else {
  10. // override init and inject vuex init procedure
  11. // for 1.x backwards compatibility.
  12. const _init = Vue.prototype._init
  13. Vue.prototype._init = function (options = {}) {
  14. options.init = options.init
  15. ? [vuexInit].concat(options.init)
  16. : vuexInit
  17. _init.call(this, options)
  18. }
  19. }

具体我们也可以看到(9-11),我们是将options.store挂到了vue实例的this.$store身上。

  1. /**
  2. * Vuex init hook, injected into each instances init hooks list.
  3. */
  4. function vuexInit () {
  5. const options = this.$options
  6. // store injection
  7. if (options.store) {
  8. this.$store = typeof options.store === 'function'
  9. ? options.store()
  10. : options.store
  11. } else if (options.parent && options.parent.$store) {
  12. this.$store = options.parent.$store
  13. }
  14. }
  15. }

所以在实例化vue的时候,我们会把之前定义的store作为参数引入进来(即options.store),所以在vue初始化的时候,vuex也就通过插件模式融入进vue实例了。

  1. new Vue({
  2. el: '#app',
  3. store: store,
  4. })

Store

接下来我们主要研究下vuex内部的一些处理机制。

先从我们对store的初始化开始,最开始官网的例子,我们通过new Vuex.Store实例化了一个根store。

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 0
  4. },
  5. mutations: {
  6. increment (state) {
  7. state.count++
  8. }
  9. }
  10. })

紧接着就是进入vuex/src/store.js内看一下new实例时,都做了一些什么。

内容非常多,我们同样是看主干内容,在构造函数(constructor)中:

  1. (8-9)首先检查Vue(此处是自己定义的变量Vue)是否存在,如果Vue变量为“空”,且window对象中有引入Vue(此处的为window.Vue)时,就会通过插件模式将vuex初始化的内容挂载到beforeCreate钩子上(上一章的内容install)

  2. (24-33)初始化了一些store内的参数

    1. 29)**this._modules = new ModuleCollection(options)** 实例化了modules(请 先 看 下一章ModuleCollection),此处其实就是初始化了各个模块(根模块、子模块)
    2. (32)this._watcherVM为一个实例化的Vue
  3. (38-43)初始化了dispatchcommit函数

  4. (53)**installModule(this, state, [], this._modules.root)**(重点,详见后面)

  5. (57)**resetStoreVM(this, state)**(重点,详见后面)

  6. (60)plugins.forEach(plugin => plugin(this)) 执行 options.plugins里的内容(此例子中plugins为空,先不管)

  7. (62-65)与devTool有关,暂时忽略(不是重点) ```javascript let Vue

export class Store { constructor (options = {}) { // Auto install if it is not done yet and window has Vue. // To allow users to avoid auto-installation in some cases, // this code should be placed here. See #731 if (!Vue && typeof window !== ‘undefined’ && window.Vue) { install(window.Vue) }

  1. if (__DEV__) {
  2. assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
  3. assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
  4. assert(this instanceof Store, `store must be called with the new operator.`)
  5. }
  6. const {
  7. plugins = [],
  8. strict = false
  9. } = options
  10. // store internal state
  11. this._committing = false
  12. this._actions = Object.create(null)
  13. this._actionSubscribers = []
  14. this._mutations = Object.create(null)
  15. this._wrappedGetters = Object.create(null)
  16. this._modules = new ModuleCollection(options)
  17. this._modulesNamespaceMap = Object.create(null)
  18. this._subscribers = []
  19. this._watcherVM = new Vue()
  20. this._makeLocalGettersCache = Object.create(null)
  21. // bind commit and dispatch to self
  22. const store = this
  23. const { dispatch, commit } = this
  24. this.dispatch = function boundDispatch (type, payload) {
  25. return dispatch.call(store, type, payload)
  26. }
  27. this.commit = function boundCommit (type, payload, options) {
  28. return commit.call(store, type, payload, options)
  29. }
  30. // strict mode
  31. this.strict = strict
  32. const state = this._modules.root.state
  33. // init root module.
  34. // this also recursively registers all sub-modules
  35. // and collects all module getters inside this._wrappedGetters
  36. installModule(this, state, [], this._modules.root)
  37. // initialize the store vm, which is responsible for the reactivity
  38. // (also registers _wrappedGetters as computed properties)
  39. resetStoreVM(this, state)
  40. // apply plugins
  41. plugins.forEach(plugin => plugin(this))
  42. const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
  43. if (useDevtools) {
  44. devtoolPlugin(this)
  45. }

}

// … … 展示省略中间的很多内容,只保留当前相关的内容 }

  1. <a name="rC82j"></a>
  2. ## installModule
  3. 如函数名所言,此处是对各个模块的“安装”、“设置”,我们把上面的主要相关的代码拿下来分析:
  4. ```javascript
  5. const state = this._modules.root.state
  6. installModule(this, state, [], this._modules.root)
  1. 首先得到state的值 (我们在Module章节已经分析过了 {count: 0 }
  2. 我们将例子中的参数带进去看后,如下
    1. installModule(
    2. this, // 第一个参数,对应下面的 store
    3. { count: 0 }, // 第二个参数,对应下面的 rootState
    4. [], // 第三个参数,对应下面的 path
    5. module类实例 // 第四个参数,对应下面的 module
    6. )
    vuex/src/store.js
  • (2)isRoot用来判断是否为根模块(此例子![].length = true,isRoot为true)
  • (3)获取当前模块的命名空间值namespace,此处为根模块,所以命名空间值为''(初始值)。
  • (6-11)如果当前模块存在命名空间,则在store的_modulesNamespaceMap中以key-value的形式记录下来
  • (14-27)主要是为非根模块设置state值(暂时忽略)
  • (29-49)这里就是重头戏了,为模块注册mutation,action,getter和组装子模块的内容(继续往下看即可)

    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. if (store._modulesNamespaceMap[namespace] && __DEV__) {
    7. console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    8. }
    9. store._modulesNamespaceMap[namespace] = module
    10. }
    11. // set state
    12. if (!isRoot && !hot) {
    13. const parentState = getNestedState(rootState, path.slice(0, -1))
    14. const moduleName = path[path.length - 1]
    15. store._withCommit(() => {
    16. if (__DEV__) {
    17. if (moduleName in parentState) {
    18. console.warn(
    19. `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
    20. )
    21. }
    22. }
    23. Vue.set(parentState, moduleName, module.state)
    24. })
    25. }
    26. const local = module.context = makeLocalContext(store, namespace, path)
    27. module.forEachMutation((mutation, key) => {
    28. const namespacedType = namespace + key
    29. registerMutation(store, namespacedType, mutation, local)
    30. })
    31. module.forEachAction((action, key) => {
    32. const type = action.root ? key : namespace + key
    33. const handler = action.handler || action
    34. registerAction(store, type, handler, local)
    35. })
    36. module.forEachGetter((getter, key) => {
    37. const namespacedType = namespace + key
    38. registerGetter(store, namespacedType, getter, local)
    39. })
    40. module.forEachChild((child, key) => {
    41. installModule(store, rootState, path.concat(key), child, hot)
    42. })
    43. }

    local

    我们看看local是什么?

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

    从代码里看,这里的local是一个包含dispatch、commit、getters和state这些键的key-value对象。

vue/src/store.js
由于我们是根模块,noNamespace为true,所以我们只考虑true的情况,所以这里获取到的local对象的dispatch、commit、getters、state的值都为store对应的值,所以local.state= { count: 0 }

  1. function makeLocalContext (store, namespace, path) {
  2. const noNamespace = namespace === ''
  3. const local = {
  4. dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
  5. // ... ... 展示省略中间的很多内容,只保留当前相关的内容
  6. },
  7. commit: noNamespace ? store.commit : (_type, _payload, _options) => {
  8. // ... ... 展示省略中间的很多内容,只保留当前相关的内容
  9. }
  10. }
  11. // getters and state object must be gotten lazily
  12. // because they will be changed by vm update
  13. Object.defineProperties(local, {
  14. getters: {
  15. get: noNamespace
  16. ? () => store.getters
  17. : () => makeLocalGetters(store, namespace)
  18. },
  19. state: {
  20. get: () => getNestedState(store.state, path)
  21. }
  22. })
  23. return local
  24. }

registerMutation

由于我们的例子只有mutation,后面我就先深入module.forEachMutation来叙述:

  1. module.forEachMutation((mutation, key) => {
  2. const namespacedType = namespace + key
  3. registerMutation(store, namespacedType, mutation, local)
  4. })

先看module.forEachMutation,此处的module是之前提到的module类的实例,forEachMutation参数中的fn则对应此处的 (mutation, key)=>{const namespacedType = namespace + key ... }
image.png
将所有参数带进去后,内容如下

  • 根据(24-33)我们可以看出store._mutations是一个对象,他会按(8)forEach的index值,初始化一个array数组,然后把对应的mutation封装在函数wrappedMutationHandler内后push进去。

vue/src/store.js

  1. forEachMutation () {
  2. const mutations = {
  3. increment: function (state) { state.count++ }
  4. }
  5. Object.keys(mutations).forEach((key) => {
  6. (mutations[key], index) => {
  7. const namespacedType = namespace + index
  8. (store, namespacedType, mutation=mutations[key], local)=>{
  9. const entry = store._mutations[namespacedType] || (store._mutations[namespacedType] = [])
  10. entry.push(function wrappedMutationHandler (payload) {
  11. mutation.call(store, local.state, payload)
  12. })
  13. }()
  14. }
  15. })
  16. }
  17. /*
  18. (10-17)带入参数后如下
  19. const namespacedType = '0'
  20. const entry = store._mutations[namespacedType] = []
  21. entry.push(function wrappedMutationHandler (payload) {
  22. mutations['increment'].call(store, local.state, payload)
  23. })
  24. */

即最后store._mutations的值为:

  1. store._mutations = {
  2. 0: [function wrappedMutationHandler (payload) {
  3. mutations['increment'].call(store, local.state, payload)
  4. })],
  5. }

registerGetter(待完善)

vue/src/store.js

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

registerAction(待完善)

vue/src/store.js

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

resetStoreVM

上面的步骤中我们已经将module初始化完成,并且组装好了module(根模块和子模块)和其内的getters、mutations和actions,接下来我们看看resetStoreVM都做了些什么。

  1. resetStoreVM(this, state)

vue/src/store.js

  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. forEachValue(wrappedGetters, (fn, key) => {
  10. // use computed to leverage its lazy-caching mechanism
  11. // direct inline function use will lead to closure preserving oldVm.
  12. // using partial to return function with only arguments preserved in closure environment.
  13. computed[key] = partial(fn, store)
  14. Object.defineProperty(store.getters, key, {
  15. get: () => store._vm[key],
  16. enumerable: true // for local getters
  17. })
  18. })
  19. // use a Vue instance to store the state tree
  20. // suppress warnings just in case the user has added
  21. // some funky global mixins
  22. const silent = Vue.config.silent
  23. Vue.config.silent = true
  24. store._vm = new Vue({
  25. data: {
  26. $$state: state
  27. },
  28. computed
  29. })
  30. Vue.config.silent = silent
  31. // enable strict mode for new vm
  32. if (store.strict) {
  33. enableStrictMode(store)
  34. }
  35. if (oldVm) {
  36. if (hot) {
  37. // dispatch changes in all subscribed watchers
  38. // to force getter re-evaluation for hot reloading.
  39. store._withCommit(() => {
  40. oldVm._data.$$state = null
  41. })
  42. }
  43. Vue.nextTick(() => oldVm.$destroy())
  44. }
  45. }

ModuleCollection

在实例化store的时候,我们注意到store中的_this.modules是ModuleCollection的实例化,通过名字我们很容易猜测到这里和模块有什么关系,也许模块、命名空间相关的内容我们都可以在这里找到。

  1. this._modules = new ModuleCollection(options)

主要内容

  • 实例化的第一步自然是走constrcutor的过程,这里执行了(4)this.register([], rawRootModule, false)
  • 深入到(15-34)的register函数,我们可以把参数具体化了看

    1. this.register(
    2. [], // 第一个参数,对应下面的path
    3. { // 第二个参数,对应下面的rawModule
    4. state: {
    5. count: 0
    6. },
    7. mutations: {
    8. increment (state) {
    9. state.count++
    10. }
    11. }
    12. }, // 即后面的rawModule对象
    13. false // 第三个参数,对应下面的runtime
    14. )
  • (20)const newModule = new Module(rawModule, true) 这里是将Module实例化了,这里涉及到了Module类(后面有图解ModuleCollection和Module的关系图)

  • (21)满足条件[].length === 0所以当前module-collection实例(也就是上面的this._modules)的root值就为这里的newModule了
  • (29-33)由于官方的例子没有子模块(即没有rawModule.modules),所以这里的内容就可以忽略了,其实这里就是注册(遍历)子模块,过程与根模块类似。

vuex/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. // ... ... 展示省略中间的很多内容,只保留当前相关的内容
  12. register (path, rawModule, runtime = true) {
  13. if (__DEV__) {
  14. assertRawModule(path, rawModule)
  15. }
  16. const newModule = new Module(rawModule, runtime)
  17. if (path.length === 0) {
  18. this.root = newModule
  19. } else {
  20. const parent = this.get(path.slice(0, -1))
  21. parent.addChild(path[path.length - 1], newModule)
  22. }
  23. // register nested modules
  24. if (rawModule.modules) {
  25. forEachValue(rawModule.modules, (rawChildModule, key) => {
  26. this.register(path.concat(key), rawChildModule, runtime)
  27. })
  28. }
  29. }
  30. // ... ... 展示省略中间的很多内容,只保留当前相关的内容
  31. }

获取命名空间值

vuex/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. }

Module

这里用图片表示一下ModuleCollection和Module的联系:
image.png

  1. /*
  2. 此处传递的 rawModule 值为
  3. {
  4. state: {
  5. count: 0
  6. },
  7. mutations: {
  8. increment (state) {
  9. state.count++
  10. }
  11. }
  12. }
  13. */
  14. const newModule = new Module(rawModule, runtime)

vuex/src/module/module.js 如下,

  • (11)所以此处newModule.state的值为{count: 0 }
  • (10)this._rawModule的值为{ state: {count: 0}, mutation: {...} } ```javascript import { forEachValue } from ‘../util’

// Base data struct for store’s module, package with some attribute and method export default class Module { constructor (rawModule, runtime) { this.runtime = runtime // Store some children item this._children = Object.create(null) // Store the origin module object which passed by programmer this._rawModule = rawModule const rawState = rawModule.state

  1. // Store the origin module's state
  2. this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}

}

// … … 展示省略中间的很多内容,只保留当前相关的内容

}

```