index.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import mixin from './mixin'
  2. import Cursor from './cursor'
  3. import devtoolMiddleware from './middlewares/devtool'
  4. import loggerMiddleware from './middlewares/logger'
  5. let Vue
  6. export { loggerMiddleware }
  7. export default class Vuex {
  8. /**
  9. * @param {Object} options
  10. * - {Object} state
  11. * - {Object} actions
  12. * - {Object} mutations
  13. * - {Array} middlewares
  14. */
  15. constructor ({
  16. state = {},
  17. actions = {},
  18. mutations = {},
  19. middlewares = []
  20. } = {}) {
  21. // use a Vue instance to store the state tree
  22. this._vm = new Vue({
  23. data: state
  24. })
  25. // create actions
  26. this.actions = Object.create(null)
  27. actions = Array.isArray(actions)
  28. ? mergeObjects(actions)
  29. : actions
  30. Object.keys(actions).forEach(name => {
  31. this.actions[name] = createAction(actions[name], this)
  32. })
  33. // mutations
  34. this._mutations = Array.isArray(mutations)
  35. ? mergeObjects(mutations, true)
  36. : mutations
  37. // middlewares
  38. this._middlewares = [devtoolMiddleware].concat(middlewares)
  39. this._needSnapshots = middlewares.some(m => m.snapshot)
  40. const initialSnapshot = this._prevSnapshot = this._needSnapshots
  41. ? deepClone(state)
  42. : null
  43. // call init hooks
  44. this._middlewares.forEach(m => {
  45. if (m.onInit) {
  46. m.onInit(m.snapshot ? initialSnapshot : state)
  47. }
  48. })
  49. }
  50. /**
  51. * "Get" the store's state, or a part of it.
  52. * Returns a Cursor, which can be subscribed to for change,
  53. * and disposed of when no longer needed.
  54. *
  55. * @param {String} [path]
  56. * @return {Cursor}
  57. */
  58. get (path) {
  59. return new Cursor(this._vm, path)
  60. }
  61. /**
  62. * Dispatch an action.
  63. *
  64. * @param {String} type
  65. */
  66. dispatch (type, ...payload) {
  67. const mutation = this._mutations[type]
  68. const prevSnapshot = this._prevSnapshot
  69. const state = this.state
  70. let snapshot, clonedPayload
  71. if (mutation) {
  72. // apply the mutation
  73. if (Array.isArray(mutation)) {
  74. mutation.forEach(m => m(state, ...payload))
  75. } else {
  76. mutation(state, ...payload)
  77. }
  78. // invoke middlewares
  79. if (this._needSnapshots) {
  80. snapshot = this._prevSnapshot = deepClone(state)
  81. clonedPayload = deepClone(payload)
  82. }
  83. this._middlewares.forEach(m => {
  84. if (m.snapshot) {
  85. m.onMutation({ type, payload: clonedPayload }, snapshot, prevSnapshot)
  86. } else {
  87. m.onMutation({ type, payload }, state)
  88. }
  89. })
  90. } else {
  91. console.warn(`[vuex] Unknown mutation: ${ type }`)
  92. }
  93. }
  94. /**
  95. * Getter for the entire state tree.
  96. *
  97. * @return {Object}
  98. */
  99. get state () {
  100. return this._vm._data
  101. }
  102. /**
  103. * Expose the logger middleware
  104. */
  105. static get loggerMiddleware () {
  106. return loggerMiddleware
  107. }
  108. }
  109. /**
  110. * Exposed install method
  111. */
  112. Vuex.install = function (_Vue) {
  113. Vue = _Vue
  114. Vue.mixin(mixin)
  115. }
  116. /**
  117. * Create a actual callable action function.
  118. *
  119. * @param {String|Function} action
  120. * @param {Vuex} vuex
  121. * @return {Function} [description]
  122. */
  123. function createAction (action, vuex) {
  124. if (typeof action === 'string') {
  125. // simple action string shorthand
  126. return (...payload) => {
  127. vuex.dispatch(action, ...payload)
  128. }
  129. } else if (typeof action === 'function') {
  130. // thunk action
  131. return (...args) => {
  132. const dispatch = (...args) => {
  133. vuex.dispatch(...args)
  134. }
  135. action(...args)(dispatch, vuex.state)
  136. }
  137. }
  138. }
  139. /**
  140. * Merge an array of objects into one.
  141. *
  142. * @param {Array<Object>} arr
  143. * @param {Boolean} allowDuplicate
  144. * @return {Object}
  145. */
  146. function mergeObjects (arr, allowDuplicate) {
  147. return arr.reduce((prev, obj) => {
  148. Object.keys(obj).forEach(key => {
  149. const existing = prev[key]
  150. if (existing) {
  151. // allow multiple mutation objects to contain duplicate
  152. // handlers for the same mutation type
  153. if (allowDuplicate) {
  154. if (Array.isArray(existing)) {
  155. existing.push(obj[key])
  156. } else {
  157. prev[key] = [prev[key], obj[key]]
  158. }
  159. } else {
  160. console.warn(`[vuex] Duplicate action: ${ key }`)
  161. }
  162. } else {
  163. prev[key] = obj[key]
  164. }
  165. })
  166. return prev
  167. }, {})
  168. }
  169. function deepClone (obj) {
  170. if (Array.isArray(obj)) {
  171. return obj.map(deepClone)
  172. } else if (obj && typeof obj === 'object') {
  173. var cloned = {}
  174. var keys = Object.keys(obj)
  175. for (var i = 0, l = keys.length; i < l; i++) {
  176. var key = keys[i]
  177. cloned[key] = deepClone(obj[key])
  178. }
  179. return cloned
  180. } else {
  181. return obj
  182. }
  183. }