index.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { mergeObjects, deepClone } from './util'
  2. import devtoolMiddleware from './middlewares/devtool'
  3. import createLogger from './middlewares/logger'
  4. import override from './override'
  5. let Vue
  6. export class Store {
  7. /**
  8. * @param {Object} options
  9. * - {Object} state
  10. * - {Object} actions
  11. * - {Object} mutations
  12. * - {Array} middlewares
  13. * - {Boolean} strict
  14. */
  15. constructor ({
  16. state = {},
  17. mutations = {},
  18. modules = {},
  19. middlewares = [],
  20. strict = false
  21. } = {}) {
  22. this._dispatching = false
  23. this._rootMutations = this._mutations = mutations
  24. this._modules = modules
  25. // bind dispatch to self
  26. const dispatch = this.dispatch
  27. this.dispatch = (...args) => {
  28. dispatch.apply(this, args)
  29. }
  30. // use a Vue instance to store the state tree
  31. this._vm = new Vue({
  32. data: state
  33. })
  34. this._setupModuleState(state, modules)
  35. this._setupModuleMutations(modules)
  36. this._setupMiddlewares(middlewares, state)
  37. // add extra warnings in strict mode
  38. if (strict) {
  39. this._setupMutationCheck()
  40. }
  41. }
  42. /**
  43. * Getter for the entire state tree.
  44. * Read only.
  45. *
  46. * @return {Object}
  47. */
  48. get state () {
  49. return this._vm._data
  50. }
  51. set state (v) {
  52. throw new Error('[vuex] Vuex root state is read only.')
  53. }
  54. /**
  55. * Dispatch an action.
  56. *
  57. * @param {String} type
  58. */
  59. dispatch (type, ...payload) {
  60. const mutation = this._mutations[type]
  61. const prevSnapshot = this._prevSnapshot
  62. const state = this.state
  63. let snapshot, clonedPayload
  64. if (mutation) {
  65. this._dispatching = true
  66. // apply the mutation
  67. if (Array.isArray(mutation)) {
  68. mutation.forEach(m => m(state, ...payload))
  69. } else {
  70. mutation(state, ...payload)
  71. }
  72. this._dispatching = false
  73. // invoke middlewares
  74. if (this._needSnapshots) {
  75. snapshot = this._prevSnapshot = deepClone(state)
  76. clonedPayload = deepClone(payload)
  77. }
  78. this._middlewares.forEach(m => {
  79. if (m.onMutation) {
  80. if (m.snapshot) {
  81. m.onMutation({ type, payload: clonedPayload }, snapshot, prevSnapshot)
  82. } else {
  83. m.onMutation({ type, payload }, state)
  84. }
  85. }
  86. })
  87. } else {
  88. console.warn(`[vuex] Unknown mutation: ${ type }`)
  89. }
  90. }
  91. /**
  92. * Hot update actions and mutations.
  93. *
  94. * @param {Object} options
  95. * - {Object} [mutations]
  96. * - {Object} [modules]
  97. */
  98. hotUpdate ({ mutations, modules } = {}) {
  99. this._rootMutations = this._mutations = mutations || this._rootMutations
  100. this._setupModuleMutations(modules || this._modules)
  101. }
  102. /**
  103. * Attach sub state tree of each module to the root tree.
  104. *
  105. * @param {Object} state
  106. * @param {Object} modules
  107. */
  108. _setupModuleState (state, modules) {
  109. const { setPath } = Vue.parsers.path
  110. Object.keys(modules).forEach(key => {
  111. setPath(state, key, modules[key].state)
  112. })
  113. }
  114. /**
  115. * Bind mutations for each module to its sub tree and
  116. * merge them all into one final mutations map.
  117. *
  118. * @param {Object} modules
  119. */
  120. _setupModuleMutations (modules) {
  121. this._modules = modules
  122. const { getPath } = Vue.parsers.path
  123. const allMutations = [this._rootMutations]
  124. Object.keys(modules).forEach(key => {
  125. const module = modules[key]
  126. // bind mutations to sub state tree
  127. const mutations = {}
  128. Object.keys(module.mutations).forEach(name => {
  129. const original = module.mutations[name]
  130. mutations[name] = (state, ...args) => {
  131. original(getPath(state, key), ...args)
  132. }
  133. })
  134. allMutations.push(mutations)
  135. })
  136. this._mutations = mergeObjects(allMutations)
  137. }
  138. /**
  139. * Setup mutation check: if the vuex instance's state is mutated
  140. * outside of a mutation handler, we throw en error. This effectively
  141. * enforces all mutations to the state to be trackable and hot-reloadble.
  142. * However, this comes at a run time cost since we are doing a deep
  143. * watch on the entire state tree, so it is only enalbed with the
  144. * strict option is set to true.
  145. */
  146. _setupMutationCheck () {
  147. // a hack to get the watcher constructor from older versions of Vue
  148. // mainly because the public $watch method does not allow sync
  149. // watchers.
  150. const unwatch = this._vm.$watch('__vuex__', a => a)
  151. const Watcher = this._vm._watchers[0].constructor
  152. unwatch()
  153. new Watcher(this._vm, '$data', () => {
  154. if (!this._dispatching) {
  155. throw new Error(
  156. '[vuex] Do not mutate vuex store state outside mutation handlers.'
  157. )
  158. }
  159. }, { deep: true, sync: true })
  160. }
  161. /**
  162. * Setup the middlewares. The devtools middleware is always
  163. * included, since it does nothing if no devtool is detected.
  164. *
  165. * A middleware can demand the state it receives to be
  166. * "snapshots", i.e. deep clones of the actual state tree.
  167. *
  168. * @param {Array} middlewares
  169. * @param {Object} state
  170. */
  171. _setupMiddlewares (middlewares, state) {
  172. this._middlewares = [devtoolMiddleware].concat(middlewares)
  173. this._needSnapshots = middlewares.some(m => m.snapshot)
  174. if (this._needSnapshots) {
  175. console.log(
  176. '[vuex] One or more of your middlewares are taking state snapshots ' +
  177. 'for each mutation. Make sure to use them only during development.'
  178. )
  179. }
  180. const initialSnapshot = this._prevSnapshot = this._needSnapshots
  181. ? deepClone(state)
  182. : null
  183. // call init hooks
  184. this._middlewares.forEach(m => {
  185. if (m.onInit) {
  186. m.onInit(m.snapshot ? initialSnapshot : state)
  187. }
  188. })
  189. }
  190. }
  191. function install (_Vue) {
  192. Vue = _Vue
  193. override(Vue)
  194. }
  195. export {
  196. install,
  197. createLogger
  198. }
  199. // also export the default
  200. export default {
  201. Store,
  202. install,
  203. createLogger
  204. }