image.png
https://tech.meituan.com/2017/04/27/vuex-code-analysis.html

Vuex 3.x 源码解析

index.js 对外暴漏了可以访问的方法:

  • install:vuex 的安装 plugin
  • Store 类
  • mapState, mapMutations, mapGetters, mapAction ```javascript import { Store, install } from ‘./store’ import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from ‘./helpers’ import createLogger from ‘./plugins/logger’

export default { Store, install, version: ‘VERSION‘, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers, createLogger }

export { Store, install, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers, createLogger }

  1. Vuex 一共有三个核心类:
  2. - Store
  3. - Module
  4. - ModuleCollection
  5. Store 类主要有以下几个方法:
  6. - commit
  7. - dispatch
  8. - subscribe
  9. - subscribeAction
  10. - watch
  11. - replaceState
  12. - registerModule
  13. - unregisterModule
  14. - hasModule
  15. - hotUpdate
  16. - _withCommit
  17. Store Utils 核心方法:
  18. - genericSubscribe
  19. - resetStore
  20. - resetStoreVMresetStoreState
  21. - installModule
  22. - makeLocalContext
  23. - makeLocalGetters
  24. - registerMutation
  25. - registerAction
  26. - registerGetter
  27. - enableStrictMode
  28. - getNestedState
  29. - unifyObjectStyle
  30. ```javascript
  31. import applyMixin from './mixin'
  32. import devtoolPlugin from './plugins/devtool'
  33. import ModuleCollection from './module/module-collection'
  34. import { forEachValue, isObject, isPromise, assert, partial } from './util'
  35. let Vue // bind on install
  36. export class Store {
  37. constructor (options = {}) {
  38. // Auto install if it is not done yet and `window` has `Vue`.
  39. // To allow users to avoid auto-installation in some cases,
  40. // this code should be placed here. See #731
  41. if (!Vue && typeof window !== 'undefined' && window.Vue) {
  42. install(window.Vue)
  43. }
  44. if (__DEV__) {
  45. assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
  46. assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
  47. assert(this instanceof Store, `store must be called with the new operator.`)
  48. }
  49. const {
  50. plugins = [],
  51. strict = false
  52. } = options
  53. // store internal state
  54. this._committing = false
  55. this._actions = Object.create(null)
  56. this._actionSubscribers = []
  57. this._mutations = Object.create(null)
  58. this._wrappedGetters = Object.create(null)
  59. this._modules = new ModuleCollection(options)
  60. this._modulesNamespaceMap = Object.create(null)
  61. this._subscribers = []
  62. this._watcherVM = new Vue()
  63. this._makeLocalGettersCache = Object.create(null)
  64. // bind commit and dispatch to self
  65. const store = this
  66. const { dispatch, commit } = this
  67. this.dispatch = function boundDispatch (type, payload) {
  68. return dispatch.call(store, type, payload)
  69. }
  70. this.commit = function boundCommit (type, payload, options) {
  71. return commit.call(store, type, payload, options)
  72. }
  73. // strict mode
  74. this.strict = strict
  75. const state = this._modules.root.state
  76. // init root module.
  77. // this also recursively registers all sub-modules
  78. // and collects all module getters inside this._wrappedGetters
  79. installModule(this, state, [], this._modules.root)
  80. // initialize the store vm, which is responsible for the reactivity
  81. // (also registers _wrappedGetters as computed properties)
  82. resetStoreVM(this, state)
  83. // apply plugins
  84. plugins.forEach(plugin => plugin(this))
  85. const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools
  86. if (useDevtools) {
  87. devtoolPlugin(this)
  88. }
  89. }
  90. get state () {
  91. return this._vm._data.$$state
  92. }
  93. set state (v) {
  94. if (__DEV__) {
  95. assert(false, `use store.replaceState() to explicit replace store state.`)
  96. }
  97. }
  98. commit (_type, _payload, _options) {
  99. // check object-style commit
  100. const {
  101. type,
  102. payload,
  103. options
  104. } = unifyObjectStyle(_type, _payload, _options)
  105. const mutation = { type, payload }
  106. const entry = this._mutations[type]
  107. if (!entry) {
  108. if (__DEV__) {
  109. console.error(`[vuex] unknown mutation type: ${type}`)
  110. }
  111. return
  112. }
  113. this._withCommit(() => {
  114. entry.forEach(function commitIterator (handler) {
  115. handler(payload)
  116. })
  117. })
  118. this._subscribers
  119. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  120. .forEach(sub => sub(mutation, this.state))
  121. if (
  122. __DEV__ &&
  123. options && options.silent
  124. ) {
  125. console.warn(
  126. `[vuex] mutation type: ${type}. Silent option has been removed. ` +
  127. 'Use the filter functionality in the vue-devtools'
  128. )
  129. }
  130. }
  131. dispatch (_type, _payload) {
  132. // check object-style dispatch
  133. const {
  134. type,
  135. payload
  136. } = unifyObjectStyle(_type, _payload)
  137. const action = { type, payload }
  138. const entry = this._actions[type]
  139. if (!entry) {
  140. if (__DEV__) {
  141. console.error(`[vuex] unknown action type: ${type}`)
  142. }
  143. return
  144. }
  145. try {
  146. this._actionSubscribers
  147. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  148. .filter(sub => sub.before)
  149. .forEach(sub => sub.before(action, this.state))
  150. } catch (e) {
  151. if (__DEV__) {
  152. console.warn(`[vuex] error in before action subscribers: `)
  153. console.error(e)
  154. }
  155. }
  156. const result = entry.length > 1
  157. ? Promise.all(entry.map(handler => handler(payload)))
  158. : entry[0](payload)
  159. return new Promise((resolve, reject) => {
  160. result.then(res => {
  161. try {
  162. this._actionSubscribers
  163. .filter(sub => sub.after)
  164. .forEach(sub => sub.after(action, this.state))
  165. } catch (e) {
  166. if (__DEV__) {
  167. console.warn(`[vuex] error in after action subscribers: `)
  168. console.error(e)
  169. }
  170. }
  171. resolve(res)
  172. }, error => {
  173. try {
  174. this._actionSubscribers
  175. .filter(sub => sub.error)
  176. .forEach(sub => sub.error(action, this.state, error))
  177. } catch (e) {
  178. if (__DEV__) {
  179. console.warn(`[vuex] error in error action subscribers: `)
  180. console.error(e)
  181. }
  182. }
  183. reject(error)
  184. })
  185. })
  186. }
  187. subscribe (fn, options) {
  188. return genericSubscribe(fn, this._subscribers, options)
  189. }
  190. subscribeAction (fn, options) {
  191. const subs = typeof fn === 'function' ? { before: fn } : fn
  192. return genericSubscribe(subs, this._actionSubscribers, options)
  193. }
  194. watch (getter, cb, options) {
  195. if (__DEV__) {
  196. assert(typeof getter === 'function', `store.watch only accepts a function.`)
  197. }
  198. return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
  199. }
  200. replaceState (state) {
  201. this._withCommit(() => {
  202. this._vm._data.$$state = state
  203. })
  204. }
  205. registerModule (path, rawModule, options = {}) {
  206. if (typeof path === 'string') path = [path]
  207. if (__DEV__) {
  208. assert(Array.isArray(path), `module path must be a string or an Array.`)
  209. assert(path.length > 0, 'cannot register the root module by using registerModule.')
  210. }
  211. this._modules.register(path, rawModule)
  212. installModule(this, this.state, path, this._modules.get(path), options.preserveState)
  213. // reset store to update getters...
  214. resetStoreVM(this, this.state)
  215. }
  216. unregisterModule (path) {
  217. if (typeof path === 'string') path = [path]
  218. if (__DEV__) {
  219. assert(Array.isArray(path), `module path must be a string or an Array.`)
  220. }
  221. this._modules.unregister(path)
  222. this._withCommit(() => {
  223. const parentState = getNestedState(this.state, path.slice(0, -1))
  224. Vue.delete(parentState, path[path.length - 1])
  225. })
  226. resetStore(this)
  227. }
  228. hasModule (path) {
  229. if (typeof path === 'string') path = [path]
  230. if (__DEV__) {
  231. assert(Array.isArray(path), `module path must be a string or an Array.`)
  232. }
  233. return this._modules.isRegistered(path)
  234. }
  235. hotUpdate (newOptions) {
  236. this._modules.update(newOptions)
  237. resetStore(this, true)
  238. }
  239. _withCommit (fn) {
  240. const committing = this._committing
  241. this._committing = true
  242. fn()
  243. this._committing = committing
  244. }
  245. }
  246. function genericSubscribe (fn, subs, options) {
  247. if (subs.indexOf(fn) < 0) {
  248. options && options.prepend
  249. ? subs.unshift(fn)
  250. : subs.push(fn)
  251. }
  252. return () => {
  253. const i = subs.indexOf(fn)
  254. if (i > -1) {
  255. subs.splice(i, 1)
  256. }
  257. }
  258. }
  259. function resetStore (store, hot) {
  260. store._actions = Object.create(null)
  261. store._mutations = Object.create(null)
  262. store._wrappedGetters = Object.create(null)
  263. store._modulesNamespaceMap = Object.create(null)
  264. const state = store.state
  265. // init all modules
  266. installModule(store, state, [], store._modules.root, true)
  267. // reset vm
  268. resetStoreVM(store, state, hot)
  269. }
  270. function resetStoreVM (store, state, hot) {
  271. const oldVm = store._vm
  272. // bind store public getters
  273. store.getters = {}
  274. // reset local getters cache
  275. store._makeLocalGettersCache = Object.create(null)
  276. const wrappedGetters = store._wrappedGetters
  277. const computed = {}
  278. forEachValue(wrappedGetters, (fn, key) => {
  279. // use computed to leverage its lazy-caching mechanism
  280. // direct inline function use will lead to closure preserving oldVm.
  281. // using partial to return function with only arguments preserved in closure environment.
  282. computed[key] = partial(fn, store)
  283. Object.defineProperty(store.getters, key, {
  284. get: () => store._vm[key],
  285. enumerable: true // for local getters
  286. })
  287. })
  288. // use a Vue instance to store the state tree
  289. // suppress warnings just in case the user has added
  290. // some funky global mixins
  291. const silent = Vue.config.silent
  292. Vue.config.silent = true
  293. store._vm = new Vue({
  294. data: {
  295. $$state: state
  296. },
  297. computed
  298. })
  299. Vue.config.silent = silent
  300. // enable strict mode for new vm
  301. if (store.strict) {
  302. enableStrictMode(store)
  303. }
  304. if (oldVm) {
  305. if (hot) {
  306. // dispatch changes in all subscribed watchers
  307. // to force getter re-evaluation for hot reloading.
  308. store._withCommit(() => {
  309. oldVm._data.$$state = null
  310. })
  311. }
  312. Vue.nextTick(() => oldVm.$destroy())
  313. }
  314. }
  315. function installModule (store, rootState, path, module, hot) {
  316. const isRoot = !path.length
  317. const namespace = store._modules.getNamespace(path)
  318. // register in namespace map
  319. if (module.namespaced) {
  320. if (store._modulesNamespaceMap[namespace] && __DEV__) {
  321. console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
  322. }
  323. store._modulesNamespaceMap[namespace] = module
  324. }
  325. // set state
  326. if (!isRoot && !hot) {
  327. const parentState = getNestedState(rootState, path.slice(0, -1))
  328. const moduleName = path[path.length - 1]
  329. store._withCommit(() => {
  330. if (__DEV__) {
  331. if (moduleName in parentState) {
  332. console.warn(
  333. `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
  334. )
  335. }
  336. }
  337. Vue.set(parentState, moduleName, module.state)
  338. })
  339. }
  340. const local = module.context = makeLocalContext(store, namespace, path)
  341. module.forEachMutation((mutation, key) => {
  342. const namespacedType = namespace + key
  343. registerMutation(store, namespacedType, mutation, local)
  344. })
  345. module.forEachAction((action, key) => {
  346. const type = action.root ? key : namespace + key
  347. const handler = action.handler || action
  348. registerAction(store, type, handler, local)
  349. })
  350. module.forEachGetter((getter, key) => {
  351. const namespacedType = namespace + key
  352. registerGetter(store, namespacedType, getter, local)
  353. })
  354. module.forEachChild((child, key) => {
  355. installModule(store, rootState, path.concat(key), child, hot)
  356. })
  357. }
  358. /**
  359. * make localized dispatch, commit, getters and state
  360. * if there is no namespace, just use root ones
  361. */
  362. function makeLocalContext (store, namespace, path) {
  363. const noNamespace = namespace === ''
  364. const local = {
  365. dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
  366. const args = unifyObjectStyle(_type, _payload, _options)
  367. const { payload, options } = args
  368. let { type } = args
  369. if (!options || !options.root) {
  370. type = namespace + type
  371. if (__DEV__ && !store._actions[type]) {
  372. console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
  373. return
  374. }
  375. }
  376. return store.dispatch(type, payload)
  377. },
  378. commit: noNamespace ? store.commit : (_type, _payload, _options) => {
  379. const args = unifyObjectStyle(_type, _payload, _options)
  380. const { payload, options } = args
  381. let { type } = args
  382. if (!options || !options.root) {
  383. type = namespace + type
  384. if (__DEV__ && !store._mutations[type]) {
  385. console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
  386. return
  387. }
  388. }
  389. store.commit(type, payload, options)
  390. }
  391. }
  392. // getters and state object must be gotten lazily
  393. // because they will be changed by vm update
  394. Object.defineProperties(local, {
  395. getters: {
  396. get: noNamespace
  397. ? () => store.getters
  398. : () => makeLocalGetters(store, namespace)
  399. },
  400. state: {
  401. get: () => getNestedState(store.state, path)
  402. }
  403. })
  404. return local
  405. }
  406. function makeLocalGetters (store, namespace) {
  407. if (!store._makeLocalGettersCache[namespace]) {
  408. const gettersProxy = {}
  409. const splitPos = namespace.length
  410. Object.keys(store.getters).forEach(type => {
  411. // skip if the target getter is not match this namespace
  412. if (type.slice(0, splitPos) !== namespace) return
  413. // extract local getter type
  414. const localType = type.slice(splitPos)
  415. // Add a port to the getters proxy.
  416. // Define as getter property because
  417. // we do not want to evaluate the getters in this time.
  418. Object.defineProperty(gettersProxy, localType, {
  419. get: () => store.getters[type],
  420. enumerable: true
  421. })
  422. })
  423. store._makeLocalGettersCache[namespace] = gettersProxy
  424. }
  425. return store._makeLocalGettersCache[namespace]
  426. }
  427. function registerMutation (store, type, handler, local) {
  428. const entry = store._mutations[type] || (store._mutations[type] = [])
  429. entry.push(function wrappedMutationHandler (payload) {
  430. handler.call(store, local.state, payload)
  431. })
  432. }
  433. function registerAction (store, type, handler, local) {
  434. const entry = store._actions[type] || (store._actions[type] = [])
  435. entry.push(function wrappedActionHandler (payload) {
  436. let res = handler.call(store, {
  437. dispatch: local.dispatch,
  438. commit: local.commit,
  439. getters: local.getters,
  440. state: local.state,
  441. rootGetters: store.getters,
  442. rootState: store.state
  443. }, payload)
  444. if (!isPromise(res)) {
  445. res = Promise.resolve(res)
  446. }
  447. if (store._devtoolHook) {
  448. return res.catch(err => {
  449. store._devtoolHook.emit('vuex:error', err)
  450. throw err
  451. })
  452. } else {
  453. return res
  454. }
  455. })
  456. }
  457. function registerGetter (store, type, rawGetter, local) {
  458. if (store._wrappedGetters[type]) {
  459. if (__DEV__) {
  460. console.error(`[vuex] duplicate getter key: ${type}`)
  461. }
  462. return
  463. }
  464. store._wrappedGetters[type] = function wrappedGetter (store) {
  465. return rawGetter(
  466. local.state, // local state
  467. local.getters, // local getters
  468. store.state, // root state
  469. store.getters // root getters
  470. )
  471. }
  472. }
  473. function enableStrictMode (store) {
  474. store._vm.$watch(function () { return this._data.$$state }, () => {
  475. if (__DEV__) {
  476. assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
  477. }
  478. }, { deep: true, sync: true })
  479. }
  480. function getNestedState (state, path) {
  481. return path.reduce((state, key) => state[key], state)
  482. }
  483. function unifyObjectStyle (type, payload, options) {
  484. if (isObject(type) && type.type) {
  485. options = payload
  486. payload = type
  487. type = type.type
  488. }
  489. if (__DEV__) {
  490. assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
  491. }
  492. return { type, payload, options }
  493. }
  494. export function install (_Vue) {
  495. if (Vue && _Vue === Vue) {
  496. if (__DEV__) {
  497. console.error(
  498. '[vuex] already installed. Vue.use(Vuex) should be called only once.'
  499. )
  500. }
  501. return
  502. }
  503. Vue = _Vue
  504. applyMixin(Vue)
  505. }

Store 类核心原理

image.png

  1. 初始化 store 的内部状态
  2. 绑定 dispatch 和 commit

    1. const { dispatch, commit } = this
    2. this.dispatch = function boundDispatch (type, payload) {
    3. return dispatch.call(store, type, payload)
    4. }
    5. this.commit = function boundCommit (type, payload, options) {
    6. return commit.call(store, type, payload, options)
    7. }
  3. 初始化 root module:installModule(this, state, [], this._modules.root)

  4. 初始化 store state,它负责状态的响应式:resetStoreState,vuex3 是 resetStoreVM

    State 的响应式原理

    其实就是把 state 挂载到 vue 上。

State 修改方法

_withCommit是一个代理方法,所有触发mutation的进行state修改的操作都经过它,由此来统一管理监控state状态的修改。实现代码如下。

  1. _withCommit (fn) {
  2. // 保存之前的提交状态
  3. const committing = this._committing
  4. // 进行本次提交,若不设置为true,直接修改state,strict模式下,Vuex将会产生非法修改state的警告
  5. this._committing = true
  6. // 执行state的修改操作
  7. fn()
  8. // 修改完成,还原本次修改之前的状态
  9. this._committing = committing
  10. }

commit(mutation) 和 dispatch(action) 的原理

commit 和 dispatch 原理类似,都是通过触发 entry(commit 是 mutation[type],dispatch 是 action[type]),订阅者遍历执行。

  • this._subscribers.slice().forEach(sub => sub(mutation, this.state)
  • this._actionSubscribers.filter(sub => sub.before).forEach(sub => sub.before(action, this.state))
  • this._actionSubscribers.filter(sub => sub.after).forEach(sub => sub.after(action, this.state))

commit 通过 _withCommit来改变 state,
dispatch 通过下述代码拿到结果:

  1. const result = entry.length > 1
  2. ? Promise.all(entry.map(handler => handler(payload)))
  3. : entry[0](payload)

commit 源码如下:

  1. commit (_type, _payload, _options) {
  2. // check object-style commit
  3. const {
  4. type,
  5. payload,
  6. options
  7. } = unifyObjectStyle(_type, _payload, _options)
  8. const mutation = { type, payload }
  9. const entry = this._mutations[type]
  10. if (!entry) {
  11. if (__DEV__) {
  12. console.error(`[vuex] unknown mutation type: ${type}`)
  13. }
  14. return
  15. }
  16. this._withCommit(() => {
  17. entry.forEach(function commitIterator (handler) {
  18. handler(payload)
  19. })
  20. })
  21. this._subscribers
  22. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  23. .forEach(sub => sub(mutation, this.state))
  24. if (
  25. __DEV__ &&
  26. options && options.silent
  27. ) {
  28. console.warn(
  29. `[vuex] mutation type: ${type}. Silent option has been removed. ` +
  30. 'Use the filter functionality in the vue-devtools'
  31. )
  32. }
  33. }

dispatch 源码如下:

  1. dispatch (_type, _payload) {
  2. // check object-style dispatch
  3. const {
  4. type,
  5. payload
  6. } = unifyObjectStyle(_type, _payload)
  7. const action = { type, payload }
  8. const entry = this._actions[type]
  9. if (!entry) {
  10. if (__DEV__) {
  11. console.error(`[vuex] unknown action type: ${type}`)
  12. }
  13. return
  14. }
  15. try {
  16. this._actionSubscribers
  17. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  18. .filter(sub => sub.before)
  19. .forEach(sub => sub.before(action, this.state))
  20. } catch (e) {
  21. if (__DEV__) {
  22. console.warn(`[vuex] error in before action subscribers: `)
  23. console.error(e)
  24. }
  25. }
  26. const result = entry.length > 1
  27. ? Promise.all(entry.map(handler => handler(payload)))
  28. : entry[0](payload)
  29. return new Promise((resolve, reject) => {
  30. result.then(res => {
  31. try {
  32. this._actionSubscribers
  33. .filter(sub => sub.after)
  34. .forEach(sub => sub.after(action, this.state))
  35. } catch (e) {
  36. if (__DEV__) {
  37. console.warn(`[vuex] error in after action subscribers: `)
  38. console.error(e)
  39. }
  40. }
  41. resolve(res)
  42. }, error => {
  43. try {
  44. this._actionSubscribers
  45. .filter(sub => sub.error)
  46. .forEach(sub => sub.error(action, this.state, error))
  47. } catch (e) {
  48. if (__DEV__) {
  49. console.warn(`[vuex] error in error action subscribers: `)
  50. console.error(e)
  51. }
  52. }
  53. reject(error)
  54. })
  55. })
  56. }

devtools 原理

核心的 travel 原理如下:

  1. 通过 devtoolHook.on 方法注册事件:devtoolHook.on('vuex:travel-to-state')
  2. 调用 store 的 replaceState 完成旅行穿梭 ```javascript const target = typeof window !== ‘undefined’ ? window : typeof global !== ‘undefined’ ? global : {} const devtoolHook = target.VUE_DEVTOOLS_GLOBAL_HOOK

export default function devtoolPlugin (store) { if (!devtoolHook) return

store._devtoolHook = devtoolHook

devtoolHook.emit(‘vuex:init’, store)

devtoolHook.on(‘vuex:travel-to-state’, targetState => { store.replaceState(targetState) })

store.subscribe((mutation, state) => { devtoolHook.emit(‘vuex:mutation’, mutation, state) }, { prepend: true })

store.subscribeAction((action, state) => { devtoolHook.emit(‘vuex:action’, action, state) }, { prepend: true }) }

  1. <a name="yujuo"></a>
  2. ## Vuex 4.x 源码解析
  3. 相比于 vuex 3.x,vuex4 将操作 store 的一个 utils function 单独抽离了出来,并且没有提供 `install` 方法来安装 store,而是通过 `createStore` 函数来创建 Store,`useStore` hooks 来 inject(vue3 的 api)来安装 store
  4. ```javascript
  5. import { watch } from 'vue'
  6. import { storeKey } from './injectKey'
  7. import { addDevtools } from './plugins/devtool'
  8. import ModuleCollection from './module/module-collection'
  9. import { assert } from './util'
  10. import {
  11. genericSubscribe,
  12. getNestedState,
  13. installModule,
  14. resetStore,
  15. resetStoreState,
  16. unifyObjectStyle
  17. } from './store-util'
  18. export function createStore (options) {
  19. return new Store(options)
  20. }
  21. export class Store {
  22. constructor (options = {}) {
  23. if (__DEV__) {
  24. assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
  25. assert(this instanceof Store, `store must be called with the new operator.`)
  26. }
  27. const {
  28. plugins = [],
  29. strict = false,
  30. devtools
  31. } = options
  32. // store internal state
  33. this._committing = false
  34. this._actions = Object.create(null)
  35. this._actionSubscribers = []
  36. this._mutations = Object.create(null)
  37. this._wrappedGetters = Object.create(null)
  38. this._modules = new ModuleCollection(options)
  39. this._modulesNamespaceMap = Object.create(null)
  40. this._subscribers = []
  41. this._makeLocalGettersCache = Object.create(null)
  42. // EffectScope instance. when registering new getters, we wrap them inside
  43. // EffectScope so that getters (computed) would not be destroyed on
  44. // component unmount.
  45. this._scope = null
  46. this._devtools = devtools
  47. // bind commit and dispatch to self
  48. const store = this
  49. const { dispatch, commit } = this
  50. this.dispatch = function boundDispatch (type, payload) {
  51. return dispatch.call(store, type, payload)
  52. }
  53. this.commit = function boundCommit (type, payload, options) {
  54. return commit.call(store, type, payload, options)
  55. }
  56. // strict mode
  57. this.strict = strict
  58. const state = this._modules.root.state
  59. // init root module.
  60. // this also recursively registers all sub-modules
  61. // and collects all module getters inside this._wrappedGetters
  62. installModule(this, state, [], this._modules.root)
  63. // initialize the store state, which is responsible for the reactivity
  64. // (also registers _wrappedGetters as computed properties)
  65. resetStoreState(this, state)
  66. // apply plugins
  67. plugins.forEach(plugin => plugin(this))
  68. }
  69. install (app, injectKey) {
  70. app.provide(injectKey || storeKey, this)
  71. app.config.globalProperties.$store = this
  72. const useDevtools = this._devtools !== undefined
  73. ? this._devtools
  74. : __DEV__ || __VUE_PROD_DEVTOOLS__
  75. if (useDevtools) {
  76. addDevtools(app, this)
  77. }
  78. }
  79. get state () {
  80. return this._state.data
  81. }
  82. set state (v) {
  83. if (__DEV__) {
  84. assert(false, `use store.replaceState() to explicit replace store state.`)
  85. }
  86. }
  87. commit (_type, _payload, _options) {
  88. // check object-style commit
  89. const {
  90. type,
  91. payload,
  92. options
  93. } = unifyObjectStyle(_type, _payload, _options)
  94. const mutation = { type, payload }
  95. const entry = this._mutations[type]
  96. if (!entry) {
  97. if (__DEV__) {
  98. console.error(`[vuex] unknown mutation type: ${type}`)
  99. }
  100. return
  101. }
  102. this._withCommit(() => {
  103. entry.forEach(function commitIterator (handler) {
  104. handler(payload)
  105. })
  106. })
  107. this._subscribers
  108. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  109. .forEach(sub => sub(mutation, this.state))
  110. if (
  111. __DEV__ &&
  112. options && options.silent
  113. ) {
  114. console.warn(
  115. `[vuex] mutation type: ${type}. Silent option has been removed. ` +
  116. 'Use the filter functionality in the vue-devtools'
  117. )
  118. }
  119. }
  120. dispatch (_type, _payload) {
  121. // check object-style dispatch
  122. const {
  123. type,
  124. payload
  125. } = unifyObjectStyle(_type, _payload)
  126. const action = { type, payload }
  127. const entry = this._actions[type]
  128. if (!entry) {
  129. if (__DEV__) {
  130. console.error(`[vuex] unknown action type: ${type}`)
  131. }
  132. return
  133. }
  134. try {
  135. this._actionSubscribers
  136. .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
  137. .filter(sub => sub.before)
  138. .forEach(sub => sub.before(action, this.state))
  139. } catch (e) {
  140. if (__DEV__) {
  141. console.warn(`[vuex] error in before action subscribers: `)
  142. console.error(e)
  143. }
  144. }
  145. const result = entry.length > 1
  146. ? Promise.all(entry.map(handler => handler(payload)))
  147. : entry[0](payload)
  148. return new Promise((resolve, reject) => {
  149. result.then(res => {
  150. try {
  151. this._actionSubscribers
  152. .filter(sub => sub.after)
  153. .forEach(sub => sub.after(action, this.state))
  154. } catch (e) {
  155. if (__DEV__) {
  156. console.warn(`[vuex] error in after action subscribers: `)
  157. console.error(e)
  158. }
  159. }
  160. resolve(res)
  161. }, error => {
  162. try {
  163. this._actionSubscribers
  164. .filter(sub => sub.error)
  165. .forEach(sub => sub.error(action, this.state, error))
  166. } catch (e) {
  167. if (__DEV__) {
  168. console.warn(`[vuex] error in error action subscribers: `)
  169. console.error(e)
  170. }
  171. }
  172. reject(error)
  173. })
  174. })
  175. }
  176. subscribe (fn, options) {
  177. return genericSubscribe(fn, this._subscribers, options)
  178. }
  179. subscribeAction (fn, options) {
  180. const subs = typeof fn === 'function' ? { before: fn } : fn
  181. return genericSubscribe(subs, this._actionSubscribers, options)
  182. }
  183. watch (getter, cb, options) {
  184. if (__DEV__) {
  185. assert(typeof getter === 'function', `store.watch only accepts a function.`)
  186. }
  187. return watch(() => getter(this.state, this.getters), cb, Object.assign({}, options))
  188. }
  189. replaceState (state) {
  190. this._withCommit(() => {
  191. this._state.data = state
  192. })
  193. }
  194. registerModule (path, rawModule, options = {}) {
  195. if (typeof path === 'string') path = [path]
  196. if (__DEV__) {
  197. assert(Array.isArray(path), `module path must be a string or an Array.`)
  198. assert(path.length > 0, 'cannot register the root module by using registerModule.')
  199. }
  200. this._modules.register(path, rawModule)
  201. installModule(this, this.state, path, this._modules.get(path), options.preserveState)
  202. // reset store to update getters...
  203. resetStoreState(this, this.state)
  204. }
  205. unregisterModule (path) {
  206. if (typeof path === 'string') path = [path]
  207. if (__DEV__) {
  208. assert(Array.isArray(path), `module path must be a string or an Array.`)
  209. }
  210. this._modules.unregister(path)
  211. this._withCommit(() => {
  212. const parentState = getNestedState(this.state, path.slice(0, -1))
  213. delete parentState[path[path.length - 1]]
  214. })
  215. resetStore(this)
  216. }
  217. hasModule (path) {
  218. if (typeof path === 'string') path = [path]
  219. if (__DEV__) {
  220. assert(Array.isArray(path), `module path must be a string or an Array.`)
  221. }
  222. return this._modules.isRegistered(path)
  223. }
  224. hotUpdate (newOptions) {
  225. this._modules.update(newOptions)
  226. resetStore(this, true)
  227. }
  228. _withCommit (fn) {
  229. const committing = this._committing
  230. this._committing = true
  231. fn()
  232. this._committing = committing
  233. }
  234. }