123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- import applyMixin from './mixin'
- import devtoolPlugin from './plugins/devtool'
- import ModuleCollection from './module/module-collection'
- import { forEachValue, isObject, isPromise, assert } from './util'
- let Vue // bind on install
- export 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 }
- }
- export 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)
- }
|