深入解读Vuex源码

# 深入解读Vuex源码

Vuex 是什么?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex 提供了一个集中式的数据状态管理存储仓库,允许 Vue 组件对其进行数据的访问和通讯,并且 Vuex 限定了 Vue 对其数据进行操作的规则,保证数据的统一性。

# 功能特性

image

Vue Component 负责数据的渲染,Vuex 负责数据的状态管理,Vue Component 通过dispatch函数触发 Vuex 对应action函数的执行,action函数内部调用commit函数触发对应mutation函数执行,mutation函数可访问 Vuex 的 state 对象并对其进行修改,响应式的 state 数据在被修改后触发执行 Vue Component 的render函数的重载,从而把 state 数据更新到渲染视图。

# 目录结构

src
├── module                    # 模块相关操作
│   ├── module-collection.js  # 模块对象树构建
│   └── module.js             # 模块对象定义
├── plugins                   # 相关插件
│   ├── devtool.js            # 调试插件
│   └── logger.js             # 日志插件
├── helpers.js                # 相关辅助函数
├── index.cjs.js              # commonjs 入口文件
├── index.js                  # 默认入口文件
├── index.mjs                 # esModule 入口文件
├── mixin.js                  # store 对象注入实现
├── store.js                  # store 对象定义
└── util.js                   # 相关工具函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 应用实例

// store.js
import Vue from 'vue'
import Vuex from 'vuex'

// $store 属性注入
Vue.use(Vuex)

// 创建 store 对象
export default new Vuex.Store({...})
1
2
3
4
5
6
7
8
9

store 对象插入

// main.js
import Vue from 'vue'
import App from './App'
improt store from './store'

new Vue({
	el: '#root',
	store, // 通过 options 传参传入 store 对象
	render: h => h(App)
})
1
2
3
4
5
6
7
8
9
10

# Vuex 的装载与注入

查看Vue.use(plugin)方法定义,可以发现其内部会调用 plugin 的install方法。

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

查看 Vuex 源码的入口文件 index.js,install方法的定义在文件 store.js 中。

// 通过局部变量 Vue,判断是否已装载
let Vue // bind on install
...

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
    // 如果是浏览器环境上通过 CDN 方式加载 Vue,则自动执行 install 方法
    if (!Vue && typeof window !== 'undefined' && window.Vue) {
      install(window.Vue)
    }
    ...
  }
}

export function install (_Vue) {
  // 防止 Vuex 重复装载
  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// applyMixin 方法定义
export default function (Vue) {
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    // Vue2 通过 mixin 使用 hook 方式进行 store 对象注入
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    // Vue1 通过重写原型 _init 方法进行 store 对象注入
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */

  function vuexInit () {
    const options = this.$options
    // store injection
    // store 注入
    // 保证在任意组件访问 $store 属性都指向同一个 store 对象
    if (options.store) {
      // 将 store 对象注入到根组件的 $store 属性上
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
      // 将子组件的 $store 属性指向父组件的 $store 属性上
      this.$store = options.parent.$store
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 数据初始化与 module 对象树构建

// store internal state
this._committing = false // 表示 commit 状态,用于判断是否是通过 commit 修改 state 属性
this._actions = Object.create(null) // 存储封装后的 actions 集合
this._actionSubscribers = []
this._mutations = Object.create(null)
this._wrappedGetters = Object.create(null)
this._modules = new ModuleCollection(options) // 构建 module 对象树
this._modulesNamespaceMap = Object.create(null)
this._subscribers = []
this._watcherVM = new Vue()
this._makeLocalGettersCache = Object.create(null)
1
2
3
4
5
6
7
8
9
10
11
// module-collection.js
export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
  ...
  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      // 校验 module 对象结构
      assertRawModule(path, rawModule)
    }

    // 创建 module 对象,提供内部属性操作方法,如 addChild 等
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      // 根module
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    // 通过递归构建嵌套 modules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// module-collection.js
export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }
  ...
  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      // 校验 module 对象结构
      assertRawModule(path, rawModule)
    }

    // 创建 module 对象,提供内部属性操作方法,如 addChild 等
    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      // 根module
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    // 通过递归构建嵌套 modules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

    // Store the origin module's state
    // 可以允许 state 属性是一个返回一个对象的函数或对象
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
  // module 对象更行方法
  ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# commit 和 dispatch 函数配置

// bind commit and dispatch to self
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
  return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
  return commit.call(store, type, payload, options)
}
1
2
3
4
5
6
7
8
9
commit (_type, _payload, _options) {
  // check object-style commit
  // 配置参数校验和处理
  const {
    type,
    payload,
    options
  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }
  const entry = this._mutations[type]
  if (!entry) {
    if (__DEV__) {
      console.error(`[vuex] unknown mutation type: ${type}`)
    }
    return
  }
  // 用于判断是否是通过 commit 修改 state 属性
  this._withCommit(() => {
    entry.forEach(function commitIterator (handler) {
      handler(payload)
    })
  })

  // 如果有订阅函数存在,则逐个执行
  this._subscribers
    .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
    .forEach(sub => sub(mutation, this.state))

  if (
    __DEV__ &&
    options && options.silent
  ) {
    console.warn(
      `[vuex] mutation type: ${type}. Silent option has been removed. ` +
      'Use the filter functionality in the vue-devtools'
    )
  }
}

dispatch (_type, _payload) {
  // check object-style dispatch
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  const action = { type, payload }
  const entry = this._actions[type]
  if (!entry) {
    if (__DEV__) {
      console.error(`[vuex] unknown action type: ${type}`)
    }
    return
  }

  try {
    this._actionSubscribers
      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
      .filter(sub => sub.before)
      .forEach(sub => sub.before(action, this.state))
  } catch (e) {
    if (__DEV__) {
      console.warn(`[vuex] error in before action subscribers: `)
      console.error(e)
    }
  }

  // 通过异步 Promise 向 actionSubscribers 传递 action 执行结果并执行
  const result = entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)

  return new Promise((resolve, reject) => {
    result.then(res => {
      try {
        this._actionSubscribers
          .filter(sub => sub.after)
          .forEach(sub => sub.after(action, this.state))
      } catch (e) {
        if (__DEV__) {
          console.warn(`[vuex] error in after action subscribers: `)
          console.error(e)
        }
      }
      resolve(res)
    }, error => {
      try {
        this._actionSubscribers
          .filter(sub => sub.error)
          .forEach(sub => sub.error(action, this.state, error))
      } catch (e) {
        if (__DEV__) {
          console.warn(`[vuex] error in error action subscribers: `)
          console.error(e)
        }
      }
      reject(error)
    })
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

# module 安装

// strict mode
this.strict = strict

const state = this._modules.root.state

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

# 初始化 rootModule

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

  // register in namespace map
  // 注册进模块 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
  // 把模块的 state 设置到 state._vm.$data 的 $$state 属性中,其中 state._vm 定义在 resetStoreVM 中
  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)
    })
  }
  ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# module 上下文环境设置

// module上下文环境生成
const local = module.context = makeLocalContext(store, namespace, path)
1
2

# mutations、actions 以及 getters 注册

// 注册一系列 mutations 、actions 以及 getters,并将其 this 绑定到当前 store 对象
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)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 子 module 安装

// 递归安装子 module
module.forEachChild((child, key) => {
  installModule(store, rootState, path.concat(key), child, hot)
})
1
2
3
4

# plugins 注入

// apply plugins
plugins.forEach(plugin => plugin(this))
1
2
上次更新: 2022/7/5 下午5:18:20