|
@@ -1,434 +1,6 @@
|
|
|
-import applyMixin from './mixin'
|
|
|
-import devtoolPlugin from './plugins/devtool'
|
|
|
-import ModuleCollection from './module/module-collection'
|
|
|
-import { forEachValue, isObject, isPromise, assert } from './util'
|
|
|
+import { Store, install } from './store'
|
|
|
import { mapState, mapMutations, mapGetters, mapActions } from './helpers'
|
|
|
|
|
|
-let Vue // bind on install
|
|
|
-
|
|
|
-class Store {
|
|
|
- constructor (options = {}) {
|
|
|
- assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
|
|
|
- assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
|
|
|
-
|
|
|
- const {
|
|
|
- state = {},
|
|
|
- plugins = [],
|
|
|
- strict = false
|
|
|
- } = options
|
|
|
-
|
|
|
- // store internal state
|
|
|
- this._committing = false
|
|
|
- this._actions = Object.create(null)
|
|
|
- this._mutations = Object.create(null)
|
|
|
- this._wrappedGetters = Object.create(null)
|
|
|
- this._modules = new ModuleCollection(options)
|
|
|
- this._modulesNamespaceMap = Object.create(null)
|
|
|
- this._subscribers = []
|
|
|
- this._watcherVM = new Vue()
|
|
|
-
|
|
|
- // 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)
|
|
|
- }
|
|
|
-
|
|
|
- // strict mode
|
|
|
- this.strict = strict
|
|
|
-
|
|
|
- // init root module.
|
|
|
- // this also recursively registers all sub-modules
|
|
|
- // and collects all module getters inside this._wrappedGetters
|
|
|
- installModule(this, state, [], this._modules.root)
|
|
|
-
|
|
|
- // initialize the store vm, which is responsible for the reactivity
|
|
|
- // (also registers _wrappedGetters as computed properties)
|
|
|
- resetStoreVM(this, state)
|
|
|
-
|
|
|
- // apply plugins
|
|
|
- plugins.concat(devtoolPlugin).forEach(plugin => plugin(this))
|
|
|
- }
|
|
|
-
|
|
|
- get state () {
|
|
|
- return this._vm._data.$$state
|
|
|
- }
|
|
|
-
|
|
|
- set state (v) {
|
|
|
- assert(false, `Use store.replaceState() to explicit replace store state.`)
|
|
|
- }
|
|
|
-
|
|
|
- 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) {
|
|
|
- console.error(`[vuex] unknown mutation type: ${type}`)
|
|
|
- return
|
|
|
- }
|
|
|
- this._withCommit(() => {
|
|
|
- entry.forEach(function commitIterator (handler) {
|
|
|
- handler(payload)
|
|
|
- })
|
|
|
- })
|
|
|
- this._subscribers.forEach(sub => sub(mutation, this.state))
|
|
|
-
|
|
|
- if (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 entry = this._actions[type]
|
|
|
- if (!entry) {
|
|
|
- console.error(`[vuex] unknown action type: ${type}`)
|
|
|
- return
|
|
|
- }
|
|
|
- return entry.length > 1
|
|
|
- ? Promise.all(entry.map(handler => handler(payload)))
|
|
|
- : entry[0](payload)
|
|
|
- }
|
|
|
-
|
|
|
- subscribe (fn) {
|
|
|
- const subs = this._subscribers
|
|
|
- if (subs.indexOf(fn) < 0) {
|
|
|
- subs.push(fn)
|
|
|
- }
|
|
|
- return () => {
|
|
|
- const i = subs.indexOf(fn)
|
|
|
- if (i > -1) {
|
|
|
- subs.splice(i, 1)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- watch (getter, cb, options) {
|
|
|
- assert(typeof getter === 'function', `store.watch only accepts a function.`)
|
|
|
- return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
|
|
|
- }
|
|
|
-
|
|
|
- replaceState (state) {
|
|
|
- this._withCommit(() => {
|
|
|
- this._vm._data.$$state = state
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- registerModule (path, rawModule) {
|
|
|
- if (typeof path === 'string') path = [path]
|
|
|
- assert(Array.isArray(path), `module path must be a string or an Array.`)
|
|
|
- this._modules.register(path, rawModule)
|
|
|
- installModule(this, this.state, path, this._modules.get(path))
|
|
|
- // reset store to update getters...
|
|
|
- resetStoreVM(this, this.state)
|
|
|
- }
|
|
|
-
|
|
|
- unregisterModule (path) {
|
|
|
- if (typeof path === 'string') path = [path]
|
|
|
- assert(Array.isArray(path), `module path must be a string or an Array.`)
|
|
|
- this._modules.unregister(path)
|
|
|
- this._withCommit(() => {
|
|
|
- const parentState = getNestedState(this.state, path.slice(0, -1))
|
|
|
- Vue.delete(parentState, path[path.length - 1])
|
|
|
- })
|
|
|
- resetStore(this)
|
|
|
- }
|
|
|
-
|
|
|
- hotUpdate (newOptions) {
|
|
|
- this._modules.update(newOptions)
|
|
|
- resetStore(this, true)
|
|
|
- }
|
|
|
-
|
|
|
- _withCommit (fn) {
|
|
|
- const committing = this._committing
|
|
|
- this._committing = true
|
|
|
- fn()
|
|
|
- this._committing = committing
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function resetStore (store, hot) {
|
|
|
- store._actions = Object.create(null)
|
|
|
- store._mutations = Object.create(null)
|
|
|
- store._wrappedGetters = Object.create(null)
|
|
|
- store._modulesNamespaceMap = Object.create(null)
|
|
|
- const state = store.state
|
|
|
- // init all modules
|
|
|
- installModule(store, state, [], store._modules.root, true)
|
|
|
- // reset vm
|
|
|
- resetStoreVM(store, state, hot)
|
|
|
-}
|
|
|
-
|
|
|
-function resetStoreVM (store, state, hot) {
|
|
|
- const oldVm = store._vm
|
|
|
-
|
|
|
- // bind store public getters
|
|
|
- store.getters = {}
|
|
|
- const wrappedGetters = store._wrappedGetters
|
|
|
- const computed = {}
|
|
|
- forEachValue(wrappedGetters, (fn, key) => {
|
|
|
- // use computed to leverage its lazy-caching mechanism
|
|
|
- computed[key] = () => fn(store)
|
|
|
- Object.defineProperty(store.getters, key, {
|
|
|
- get: () => store._vm[key],
|
|
|
- enumerable: true // for local getters
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- // use a Vue instance to store the state tree
|
|
|
- // suppress warnings just in case the user has added
|
|
|
- // some funky global mixins
|
|
|
- const silent = Vue.config.silent
|
|
|
- Vue.config.silent = true
|
|
|
- store._vm = new Vue({
|
|
|
- data: {
|
|
|
- $$state: state
|
|
|
- },
|
|
|
- computed
|
|
|
- })
|
|
|
- Vue.config.silent = silent
|
|
|
-
|
|
|
- // enable strict mode for new vm
|
|
|
- if (store.strict) {
|
|
|
- enableStrictMode(store)
|
|
|
- }
|
|
|
-
|
|
|
- if (oldVm) {
|
|
|
- if (hot) {
|
|
|
- // dispatch changes in all subscribed watchers
|
|
|
- // to force getter re-evaluation for hot reloading.
|
|
|
- store._withCommit(() => {
|
|
|
- oldVm._data.$$state = null
|
|
|
- })
|
|
|
- }
|
|
|
- Vue.nextTick(() => oldVm.$destroy())
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function installModule (store, rootState, path, module, hot) {
|
|
|
- const isRoot = !path.length
|
|
|
- const namespace = store._modules.getNamespace(path)
|
|
|
-
|
|
|
- // register in namespace map
|
|
|
- if (namespace) {
|
|
|
- store._modulesNamespaceMap[namespace] = module
|
|
|
- }
|
|
|
-
|
|
|
- // set state
|
|
|
- if (!isRoot && !hot) {
|
|
|
- const parentState = getNestedState(rootState, path.slice(0, -1))
|
|
|
- const moduleName = path[path.length - 1]
|
|
|
- store._withCommit(() => {
|
|
|
- Vue.set(parentState, moduleName, module.state)
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- const local = module.context = makeLocalContext(store, namespace, path)
|
|
|
-
|
|
|
- module.forEachMutation((mutation, key) => {
|
|
|
- const namespacedType = namespace + key
|
|
|
- registerMutation(store, namespacedType, mutation, local)
|
|
|
- })
|
|
|
-
|
|
|
- module.forEachAction((action, key) => {
|
|
|
- const namespacedType = namespace + key
|
|
|
- registerAction(store, namespacedType, action, local)
|
|
|
- })
|
|
|
-
|
|
|
- module.forEachGetter((getter, key) => {
|
|
|
- const namespacedType = namespace + key
|
|
|
- registerGetter(store, namespacedType, getter, local)
|
|
|
- })
|
|
|
-
|
|
|
- module.forEachChild((child, key) => {
|
|
|
- installModule(store, rootState, path.concat(key), child, hot)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * make localized dispatch, commit, getters and state
|
|
|
- * if there is no namespace, just use root ones
|
|
|
- */
|
|
|
-function makeLocalContext (store, namespace, path) {
|
|
|
- const noNamespace = namespace === ''
|
|
|
-
|
|
|
- const local = {
|
|
|
- dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
|
|
|
- const args = unifyObjectStyle(_type, _payload, _options)
|
|
|
- const { payload, options } = args
|
|
|
- let { type } = args
|
|
|
-
|
|
|
- if (!options || !options.root) {
|
|
|
- type = namespace + type
|
|
|
- if (!store._actions[type]) {
|
|
|
- console.error(`[vuex] unknown local action type: ${args.type}, global type: ${type}`)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return store.dispatch(type, payload)
|
|
|
- },
|
|
|
-
|
|
|
- commit: noNamespace ? store.commit : (_type, _payload, _options) => {
|
|
|
- const args = unifyObjectStyle(_type, _payload, _options)
|
|
|
- const { payload, options } = args
|
|
|
- let { type } = args
|
|
|
-
|
|
|
- if (!options || !options.root) {
|
|
|
- type = namespace + type
|
|
|
- if (!store._mutations[type]) {
|
|
|
- console.error(`[vuex] unknown local mutation type: ${args.type}, global type: ${type}`)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- store.commit(type, payload, options)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // getters and state object must be gotten lazily
|
|
|
- // because they will be changed by vm update
|
|
|
- Object.defineProperties(local, {
|
|
|
- getters: {
|
|
|
- get: noNamespace
|
|
|
- ? () => store.getters
|
|
|
- : () => makeLocalGetters(store, namespace)
|
|
|
- },
|
|
|
- state: {
|
|
|
- get: () => getNestedState(store.state, path)
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- return local
|
|
|
-}
|
|
|
-
|
|
|
-function makeLocalGetters (store, namespace) {
|
|
|
- const gettersProxy = {}
|
|
|
-
|
|
|
- const splitPos = namespace.length
|
|
|
- Object.keys(store.getters).forEach(type => {
|
|
|
- // skip if the target getter is not match this namespace
|
|
|
- if (type.slice(0, splitPos) !== namespace) return
|
|
|
-
|
|
|
- // extract local getter type
|
|
|
- const localType = type.slice(splitPos)
|
|
|
-
|
|
|
- // Add a port to the getters proxy.
|
|
|
- // Define as getter property because
|
|
|
- // we do not want to evaluate the getters in this time.
|
|
|
- Object.defineProperty(gettersProxy, localType, {
|
|
|
- get: () => store.getters[type],
|
|
|
- enumerable: true
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- return gettersProxy
|
|
|
-}
|
|
|
-
|
|
|
-function registerMutation (store, type, handler, local) {
|
|
|
- const entry = store._mutations[type] || (store._mutations[type] = [])
|
|
|
- entry.push(function wrappedMutationHandler (payload) {
|
|
|
- handler(local.state, payload)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-function registerAction (store, type, handler, local) {
|
|
|
- const entry = store._actions[type] || (store._actions[type] = [])
|
|
|
- entry.push(function wrappedActionHandler (payload, cb) {
|
|
|
- let res = handler({
|
|
|
- dispatch: local.dispatch,
|
|
|
- commit: local.commit,
|
|
|
- getters: local.getters,
|
|
|
- state: local.state,
|
|
|
- rootGetters: store.getters,
|
|
|
- rootState: store.state
|
|
|
- }, payload, cb)
|
|
|
- if (!isPromise(res)) {
|
|
|
- res = Promise.resolve(res)
|
|
|
- }
|
|
|
- if (store._devtoolHook) {
|
|
|
- return res.catch(err => {
|
|
|
- store._devtoolHook.emit('vuex:error', err)
|
|
|
- throw err
|
|
|
- })
|
|
|
- } else {
|
|
|
- return res
|
|
|
- }
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-function registerGetter (store, type, rawGetter, local) {
|
|
|
- if (store._wrappedGetters[type]) {
|
|
|
- console.error(`[vuex] duplicate getter key: ${type}`)
|
|
|
- return
|
|
|
- }
|
|
|
- store._wrappedGetters[type] = function wrappedGetter (store) {
|
|
|
- return rawGetter(
|
|
|
- local.state, // local state
|
|
|
- local.getters, // local getters
|
|
|
- store.state, // root state
|
|
|
- store.getters // root getters
|
|
|
- )
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function enableStrictMode (store) {
|
|
|
- store._vm.$watch(function () { return this._data.$$state }, () => {
|
|
|
- assert(store._committing, `Do not mutate vuex store state outside mutation handlers.`)
|
|
|
- }, { deep: true, sync: true })
|
|
|
-}
|
|
|
-
|
|
|
-function getNestedState (state, path) {
|
|
|
- return path.length
|
|
|
- ? path.reduce((state, key) => state[key], state)
|
|
|
- : state
|
|
|
-}
|
|
|
-
|
|
|
-function unifyObjectStyle (type, payload, options) {
|
|
|
- if (isObject(type) && type.type) {
|
|
|
- options = payload
|
|
|
- payload = type
|
|
|
- type = type.type
|
|
|
- }
|
|
|
-
|
|
|
- assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)
|
|
|
-
|
|
|
- return { type, payload, options }
|
|
|
-}
|
|
|
-
|
|
|
-function install (_Vue) {
|
|
|
- if (Vue) {
|
|
|
- console.error(
|
|
|
- '[vuex] already installed. Vue.use(Vuex) should be called only once.'
|
|
|
- )
|
|
|
- return
|
|
|
- }
|
|
|
- Vue = _Vue
|
|
|
- applyMixin(Vue)
|
|
|
-}
|
|
|
-
|
|
|
-// auto install in dist mode
|
|
|
-if (typeof window !== 'undefined' && window.Vue) {
|
|
|
- install(window.Vue)
|
|
|
-}
|
|
|
-
|
|
|
export default {
|
|
|
Store,
|
|
|
install,
|