|
@@ -1,8 +1,16 @@
|
|
|
-import { reactive, watch } from 'vue'
|
|
|
+import { watch } from 'vue'
|
|
|
import { storeKey } from './injectKey'
|
|
|
-import devtoolPlugin from './plugins/devtool'
|
|
|
+import { addDevtools } from './plugins/devtool'
|
|
|
import ModuleCollection from './module/module-collection'
|
|
|
-import { forEachValue, isObject, isPromise, assert, partial } from './util'
|
|
|
+import { assert } from './util'
|
|
|
+import {
|
|
|
+ genericSubscribe,
|
|
|
+ getNestedState,
|
|
|
+ installModule,
|
|
|
+ resetStore,
|
|
|
+ resetStoreState,
|
|
|
+ unifyObjectStyle
|
|
|
+} from './store-util'
|
|
|
|
|
|
export function createStore (options) {
|
|
|
return new Store(options)
|
|
@@ -17,7 +25,8 @@ export class Store {
|
|
|
|
|
|
const {
|
|
|
plugins = [],
|
|
|
- strict = false
|
|
|
+ strict = false,
|
|
|
+ devtools
|
|
|
} = options
|
|
|
|
|
|
// store internal state
|
|
@@ -30,6 +39,7 @@ export class Store {
|
|
|
this._modulesNamespaceMap = Object.create(null)
|
|
|
this._subscribers = []
|
|
|
this._makeLocalGettersCache = Object.create(null)
|
|
|
+ this._devtools = devtools
|
|
|
|
|
|
// bind commit and dispatch to self
|
|
|
const store = this
|
|
@@ -57,16 +67,19 @@ export class Store {
|
|
|
|
|
|
// apply plugins
|
|
|
plugins.forEach(plugin => plugin(this))
|
|
|
-
|
|
|
- const useDevtools = options.devtools !== undefined ? options.devtools : /* Vue.config.devtools */ true
|
|
|
- if (useDevtools) {
|
|
|
- devtoolPlugin(this)
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
install (app, injectKey) {
|
|
|
app.provide(injectKey || storeKey, this)
|
|
|
app.config.globalProperties.$store = this
|
|
|
+
|
|
|
+ const useDevtools = this._devtools !== undefined
|
|
|
+ ? this._devtools
|
|
|
+ : __DEV__ || __VUE_PROD_DEVTOOLS__
|
|
|
+
|
|
|
+ if (useDevtools) {
|
|
|
+ addDevtools(app, this)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
get state () {
|
|
@@ -250,279 +263,3 @@ export class Store {
|
|
|
this._committing = committing
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-function genericSubscribe (fn, subs, options) {
|
|
|
- if (subs.indexOf(fn) < 0) {
|
|
|
- options && options.prepend
|
|
|
- ? subs.unshift(fn)
|
|
|
- : subs.push(fn)
|
|
|
- }
|
|
|
- return () => {
|
|
|
- const i = subs.indexOf(fn)
|
|
|
- if (i > -1) {
|
|
|
- subs.splice(i, 1)
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-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 state
|
|
|
- resetStoreState(store, state, hot)
|
|
|
-}
|
|
|
-
|
|
|
-function resetStoreState (store, state, hot) {
|
|
|
- const oldState = store._state
|
|
|
-
|
|
|
- // bind store public getters
|
|
|
- store.getters = {}
|
|
|
- // reset local getters cache
|
|
|
- store._makeLocalGettersCache = Object.create(null)
|
|
|
- const wrappedGetters = store._wrappedGetters
|
|
|
- const computedObj = {}
|
|
|
- forEachValue(wrappedGetters, (fn, key) => {
|
|
|
- // use computed to leverage its lazy-caching mechanism
|
|
|
- // direct inline function use will lead to closure preserving oldState.
|
|
|
- // using partial to return function with only arguments preserved in closure environment.
|
|
|
- computedObj[key] = partial(fn, store)
|
|
|
- Object.defineProperty(store.getters, key, {
|
|
|
- // TODO: use `computed` when it's possible. at the moment we can't due to
|
|
|
- // https://github.com/vuejs/vuex/pull/1883
|
|
|
- get: () => computedObj[key](),
|
|
|
- enumerable: true // for local getters
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- store._state = reactive({
|
|
|
- data: state
|
|
|
- })
|
|
|
-
|
|
|
- // enable strict mode for new state
|
|
|
- if (store.strict) {
|
|
|
- enableStrictMode(store)
|
|
|
- }
|
|
|
-
|
|
|
- if (oldState) {
|
|
|
- if (hot) {
|
|
|
- // dispatch changes in all subscribed watchers
|
|
|
- // to force getter re-evaluation for hot reloading.
|
|
|
- store._withCommit(() => {
|
|
|
- oldState.data = null
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function installModule (store, rootState, path, module, hot) {
|
|
|
- const isRoot = !path.length
|
|
|
- const namespace = store._modules.getNamespace(path)
|
|
|
-
|
|
|
- // register in namespace map
|
|
|
- if (module.namespaced) {
|
|
|
- if (store._modulesNamespaceMap[namespace] && __DEV__) {
|
|
|
- console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
|
|
|
- }
|
|
|
- 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(() => {
|
|
|
- if (__DEV__) {
|
|
|
- if (moduleName in parentState) {
|
|
|
- console.warn(
|
|
|
- `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
- 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 type = action.root ? key : namespace + key
|
|
|
- const handler = action.handler || action
|
|
|
- registerAction(store, type, handler, 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 (__DEV__ && !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 (__DEV__ && !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 state update
|
|
|
- Object.defineProperties(local, {
|
|
|
- getters: {
|
|
|
- get: noNamespace
|
|
|
- ? () => store.getters
|
|
|
- : () => makeLocalGetters(store, namespace)
|
|
|
- },
|
|
|
- state: {
|
|
|
- get: () => getNestedState(store.state, path)
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- return local
|
|
|
-}
|
|
|
-
|
|
|
-function makeLocalGetters (store, namespace) {
|
|
|
- if (!store._makeLocalGettersCache[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
|
|
|
- })
|
|
|
- })
|
|
|
- store._makeLocalGettersCache[namespace] = gettersProxy
|
|
|
- }
|
|
|
-
|
|
|
- return store._makeLocalGettersCache[namespace]
|
|
|
-}
|
|
|
-
|
|
|
-function registerMutation (store, type, handler, local) {
|
|
|
- const entry = store._mutations[type] || (store._mutations[type] = [])
|
|
|
- entry.push(function wrappedMutationHandler (payload) {
|
|
|
- handler.call(store, local.state, payload)
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-function registerAction (store, type, handler, local) {
|
|
|
- const entry = store._actions[type] || (store._actions[type] = [])
|
|
|
- entry.push(function wrappedActionHandler (payload) {
|
|
|
- let res = handler.call(store, {
|
|
|
- dispatch: local.dispatch,
|
|
|
- commit: local.commit,
|
|
|
- getters: local.getters,
|
|
|
- state: local.state,
|
|
|
- rootGetters: store.getters,
|
|
|
- rootState: store.state
|
|
|
- }, payload)
|
|
|
- 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]) {
|
|
|
- if (__DEV__) {
|
|
|
- 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) {
|
|
|
- watch(() => store._state.data, () => {
|
|
|
- if (__DEV__) {
|
|
|
- assert(store._committing, `do not mutate vuex store state outside mutation handlers.`)
|
|
|
- }
|
|
|
- }, { deep: true, flush: 'sync' })
|
|
|
-}
|
|
|
-
|
|
|
-function getNestedState (state, path) {
|
|
|
- return path.reduce((state, key) => state[key], state)
|
|
|
-}
|
|
|
-
|
|
|
-function unifyObjectStyle (type, payload, options) {
|
|
|
- if (isObject(type) && type.type) {
|
|
|
- options = payload
|
|
|
- payload = type
|
|
|
- type = type.type
|
|
|
- }
|
|
|
-
|
|
|
- if (__DEV__) {
|
|
|
- assert(typeof type === 'string', `expects string as the type, but found ${typeof type}.`)
|
|
|
- }
|
|
|
-
|
|
|
- return { type, payload, options }
|
|
|
-}
|