diff --git a/build/build.main.js b/build/build.main.js index bfd68446e..7f6e4e7ec 100644 --- a/build/build.main.js +++ b/build/build.main.js @@ -1,7 +1,7 @@ const fs = require('fs') const path = require('path') const zlib = require('zlib') -const uglify = require('uglify-js') +const terser = require('terser') const rollup = require('rollup') const configs = require('./configs') @@ -27,21 +27,24 @@ function build (builds) { } function buildEntry ({ input, output }) { - const isProd = /min\.js$/.test(output.file) + const { file, banner } = output + const isProd = /min\.js$/.test(file) return rollup.rollup(input) .then(bundle => bundle.generate(output)) .then(({ output: [{ code }] }) => { if (isProd) { - var minified = (output.banner ? output.banner + '\n' : '') + uglify.minify(code, { + const minified = (banner ? banner + '\n' : '') + terser.minify(code, { + toplevel: true, output: { - /* eslint-disable camelcase */ ascii_only: true - /* eslint-enable camelcase */ + }, + compress: { + pure_funcs: ['makeMap'] } }).code - return write(output.file, minified, true) + return write(file, minified, true) } else { - return write(output.file, code) + return write(file, code) } }) } diff --git a/build/configs.js b/build/configs.js index ebabe32ab..54c5f27df 100644 --- a/build/configs.js +++ b/build/configs.js @@ -33,6 +33,20 @@ const configs = { input: resolve('src/index.esm.js'), file: resolve('dist/vuex.esm.js'), format: 'es' + }, + 'esm-browser-dev': { + input: resolve('src/index.esm.js'), + file: resolve('dist/vuex.esm.browser.js'), + format: 'es', + env: 'development', + transpile: false + }, + 'esm-browser-prod': { + input: resolve('src/index.esm.js'), + file: resolve('dist/vuex.esm.browser.min.js'), + format: 'es', + env: 'production', + transpile: false } } @@ -43,8 +57,7 @@ function genConfig (opts) { plugins: [ replace({ __VERSION__: version - }), - buble() + }) ] }, output: { @@ -61,6 +74,10 @@ function genConfig (opts) { })) } + if (opts.transpile !== false) { + config.input.plugins.push(buble()) + } + return config } diff --git a/dist/vuex.common.js b/dist/vuex.common.js index 88436c580..28e5f9450 100644 --- a/dist/vuex.common.js +++ b/dist/vuex.common.js @@ -1,5 +1,5 @@ /** - * vuex v3.1.0 + * vuex v3.1.1 * (c) 2019 Evan You * @license MIT */ @@ -41,9 +41,12 @@ function applyMixin (Vue) { } } -var devtoolHook = - typeof window !== 'undefined' && - window.__VUE_DEVTOOLS_GLOBAL_HOOK__; +var target = typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {}; +var devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__; function devtoolPlugin (store) { if (!devtoolHook) { return } @@ -89,6 +92,12 @@ function assert (condition, msg) { if (!condition) { throw new Error(("[vuex] " + msg)) } } +function partial (fn, arg) { + return function () { + return fn(arg) + } +} + // Base data struct for store's module, package with some attribute and method var Module = function Module (rawModule, runtime) { this.runtime = runtime; @@ -550,7 +559,9 @@ function resetStoreVM (store, state, hot) { var computed = {}; forEachValue(wrappedGetters, function (fn, key) { // use computed to leverage its lazy-caching mechanism - computed[key] = function () { return fn(store); }; + // direct inline function use will lead to closure preserving oldVm. + // using partial to return function with only arguments preserved in closure enviroment. + computed[key] = partial(fn, store); Object.defineProperty(store.getters, key, { get: function () { return store._vm[key]; }, enumerable: true // for local getters @@ -989,7 +1000,7 @@ function getModuleByNamespace (store, helper, namespace) { var index = { Store: Store, install: install, - version: '3.1.0', + version: '3.1.1', mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, diff --git a/dist/vuex.esm.browser.js b/dist/vuex.esm.browser.js new file mode 100644 index 000000000..679a5d842 --- /dev/null +++ b/dist/vuex.esm.browser.js @@ -0,0 +1,969 @@ +/** + * vuex v3.1.1 + * (c) 2019 Evan You + * @license MIT + */ +function applyMixin (Vue) { + const version = Number(Vue.version.split('.')[0]); + + if (version >= 2) { + Vue.mixin({ beforeCreate: vuexInit }); + } else { + // override init and inject vuex init procedure + // for 1.x backwards compatibility. + const _init = Vue.prototype._init; + Vue.prototype._init = function (options = {}) { + options.init = options.init + ? [vuexInit].concat(options.init) + : vuexInit; + _init.call(this, options); + }; + } + + /** + * Vuex init hook, injected into each instances init hooks list. + */ + + function vuexInit () { + const options = this.$options; + // store injection + if (options.store) { + this.$store = typeof options.store === 'function' + ? options.store() + : options.store; + } else if (options.parent && options.parent.$store) { + this.$store = options.parent.$store; + } + } +} + +const target = typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {}; +const devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__; + +function devtoolPlugin (store) { + if (!devtoolHook) return + + store._devtoolHook = devtoolHook; + + devtoolHook.emit('vuex:init', store); + + devtoolHook.on('vuex:travel-to-state', targetState => { + store.replaceState(targetState); + }); + + store.subscribe((mutation, state) => { + devtoolHook.emit('vuex:mutation', mutation, state); + }); +} + +/** + * Get the first item that pass the test + * by second argument function + * + * @param {Array} list + * @param {Function} f + * @return {*} + */ + +/** + * forEach for object + */ +function forEachValue (obj, fn) { + Object.keys(obj).forEach(key => fn(obj[key], key)); +} + +function isObject (obj) { + return obj !== null && typeof obj === 'object' +} + +function isPromise (val) { + return val && typeof val.then === 'function' +} + +function assert (condition, msg) { + if (!condition) throw new Error(`[vuex] ${msg}`) +} + +function partial (fn, arg) { + return function () { + return fn(arg) + } +} + +// Base data struct for store's module, package with some attribute and method +class Module { + constructor (rawModule, runtime) { + this.runtime = runtime; + // Store some children item + this._children = Object.create(null); + // Store the origin module object which passed by programmer + this._rawModule = rawModule; + const rawState = rawModule.state; + + // Store the origin module's state + this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}; + } + + get namespaced () { + return !!this._rawModule.namespaced + } + + addChild (key, module) { + this._children[key] = module; + } + + removeChild (key) { + delete this._children[key]; + } + + getChild (key) { + return this._children[key] + } + + update (rawModule) { + this._rawModule.namespaced = rawModule.namespaced; + if (rawModule.actions) { + this._rawModule.actions = rawModule.actions; + } + if (rawModule.mutations) { + this._rawModule.mutations = rawModule.mutations; + } + if (rawModule.getters) { + this._rawModule.getters = rawModule.getters; + } + } + + forEachChild (fn) { + forEachValue(this._children, fn); + } + + forEachGetter (fn) { + if (this._rawModule.getters) { + forEachValue(this._rawModule.getters, fn); + } + } + + forEachAction (fn) { + if (this._rawModule.actions) { + forEachValue(this._rawModule.actions, fn); + } + } + + forEachMutation (fn) { + if (this._rawModule.mutations) { + forEachValue(this._rawModule.mutations, fn); + } + } +} + +class ModuleCollection { + constructor (rawRootModule) { + // register root module (Vuex.Store options) + this.register([], rawRootModule, false); + } + + get (path) { + return path.reduce((module, key) => { + return module.getChild(key) + }, this.root) + } + + getNamespace (path) { + let module = this.root; + return path.reduce((namespace, key) => { + module = module.getChild(key); + return namespace + (module.namespaced ? key + '/' : '') + }, '') + } + + update (rawRootModule) { + update([], this.root, rawRootModule); + } + + register (path, rawModule, runtime = true) { + { + assertRawModule(path, rawModule); + } + + const newModule = new Module(rawModule, runtime); + if (path.length === 0) { + this.root = newModule; + } else { + const parent = this.get(path.slice(0, -1)); + parent.addChild(path[path.length - 1], newModule); + } + + // register nested modules + if (rawModule.modules) { + forEachValue(rawModule.modules, (rawChildModule, key) => { + this.register(path.concat(key), rawChildModule, runtime); + }); + } + } + + unregister (path) { + const parent = this.get(path.slice(0, -1)); + const key = path[path.length - 1]; + if (!parent.getChild(key).runtime) return + + parent.removeChild(key); + } +} + +function update (path, targetModule, newModule) { + { + assertRawModule(path, newModule); + } + + // update target module + targetModule.update(newModule); + + // update nested modules + if (newModule.modules) { + for (const key in newModule.modules) { + if (!targetModule.getChild(key)) { + { + console.warn( + `[vuex] trying to add a new module '${key}' on hot reloading, ` + + 'manual reload is needed' + ); + } + return + } + update( + path.concat(key), + targetModule.getChild(key), + newModule.modules[key] + ); + } + } +} + +const functionAssert = { + assert: value => typeof value === 'function', + expected: 'function' +}; + +const objectAssert = { + assert: value => typeof value === 'function' || + (typeof value === 'object' && typeof value.handler === 'function'), + expected: 'function or object with "handler" function' +}; + +const assertTypes = { + getters: functionAssert, + mutations: functionAssert, + actions: objectAssert +}; + +function assertRawModule (path, rawModule) { + Object.keys(assertTypes).forEach(key => { + if (!rawModule[key]) return + + const assertOptions = assertTypes[key]; + + forEachValue(rawModule[key], (value, type) => { + assert( + assertOptions.assert(value), + makeAssertionMessage(path, key, type, value, assertOptions.expected) + ); + }); + }); +} + +function makeAssertionMessage (path, key, type, value, expected) { + let buf = `${key} should be ${expected} but "${key}.${type}"`; + if (path.length > 0) { + buf += ` in module "${path.join('.')}"`; + } + buf += ` is ${JSON.stringify(value)}.`; + return buf +} + +let Vue; // bind on install + +class Store { + constructor (options = {}) { + // Auto install if it is not done yet and `window` has `Vue`. + // To allow users to avoid auto-installation in some cases, + // this code should be placed here. See #731 + if (!Vue && typeof window !== 'undefined' && window.Vue) { + install(window.Vue); + } + + { + assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`); + assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`); + assert(this instanceof Store, `store must be called with the new operator.`); + } + + const { + plugins = [], + strict = false + } = options; + + // store internal state + this._committing = false; + this._actions = Object.create(null); + this._actionSubscribers = []; + 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; + + const state = this._modules.root.state; + + // 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.forEach(plugin => plugin(this)); + + const useDevtools = options.devtools !== undefined ? options.devtools : Vue.config.devtools; + if (useDevtools) { + devtoolPlugin(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 action = { type, payload }; + const entry = this._actions[type]; + if (!entry) { + { + console.error(`[vuex] unknown action type: ${type}`); + } + return + } + + try { + this._actionSubscribers + .filter(sub => sub.before) + .forEach(sub => sub.before(action, this.state)); + } catch (e) { + { + console.warn(`[vuex] error in before action subscribers: `); + console.error(e); + } + } + + const result = entry.length > 1 + ? Promise.all(entry.map(handler => handler(payload))) + : entry[0](payload); + + return result.then(res => { + try { + this._actionSubscribers + .filter(sub => sub.after) + .forEach(sub => sub.after(action, this.state)); + } catch (e) { + { + console.warn(`[vuex] error in after action subscribers: `); + console.error(e); + } + } + return res + }) + } + + subscribe (fn) { + return genericSubscribe(fn, this._subscribers) + } + + subscribeAction (fn) { + const subs = typeof fn === 'function' ? { before: fn } : fn; + return genericSubscribe(subs, this._actionSubscribers) + } + + 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, options = {}) { + if (typeof path === 'string') path = [path]; + + { + assert(Array.isArray(path), `module path must be a string or an Array.`); + assert(path.length > 0, 'cannot register the root module by using registerModule.'); + } + + this._modules.register(path, rawModule); + installModule(this, this.state, path, this._modules.get(path), options.preserveState); + // 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 genericSubscribe (fn, subs) { + if (subs.indexOf(fn) < 0) { + 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 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 + // direct inline function use will lead to closure preserving oldVm. + // using partial to return function with only arguments preserved in closure enviroment. + computed[key] = partial(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 (module.namespaced) { + 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 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 (!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.call(store, 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.call(store, { + 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 && _Vue === Vue) { + { + console.error( + '[vuex] already installed. Vue.use(Vuex) should be called only once.' + ); + } + return + } + Vue = _Vue; + applyMixin(Vue); +} + +/** + * Reduce the code which written in Vue.js for getting the state. + * @param {String} [namespace] - Module's namespace + * @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it. + * @param {Object} + */ +const mapState = normalizeNamespace((namespace, states) => { + const res = {}; + normalizeMap(states).forEach(({ key, val }) => { + res[key] = function mappedState () { + let state = this.$store.state; + let getters = this.$store.getters; + if (namespace) { + const module = getModuleByNamespace(this.$store, 'mapState', namespace); + if (!module) { + return + } + state = module.context.state; + getters = module.context.getters; + } + return typeof val === 'function' + ? val.call(this, state, getters) + : state[val] + }; + // mark vuex getter for devtools + res[key].vuex = true; + }); + return res +}); + +/** + * Reduce the code which written in Vue.js for committing the mutation + * @param {String} [namespace] - Module's namespace + * @param {Object|Array} mutations # Object's item can be a function which accept `commit` function as the first param, it can accept anthor params. You can commit mutation and do any other things in this function. specially, You need to pass anthor params from the mapped function. + * @return {Object} + */ +const mapMutations = normalizeNamespace((namespace, mutations) => { + const res = {}; + normalizeMap(mutations).forEach(({ key, val }) => { + res[key] = function mappedMutation (...args) { + // Get the commit method from store + let commit = this.$store.commit; + if (namespace) { + const module = getModuleByNamespace(this.$store, 'mapMutations', namespace); + if (!module) { + return + } + commit = module.context.commit; + } + return typeof val === 'function' + ? val.apply(this, [commit].concat(args)) + : commit.apply(this.$store, [val].concat(args)) + }; + }); + return res +}); + +/** + * Reduce the code which written in Vue.js for getting the getters + * @param {String} [namespace] - Module's namespace + * @param {Object|Array} getters + * @return {Object} + */ +const mapGetters = normalizeNamespace((namespace, getters) => { + const res = {}; + normalizeMap(getters).forEach(({ key, val }) => { + // The namespace has been mutated by normalizeNamespace + val = namespace + val; + res[key] = function mappedGetter () { + if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) { + return + } + if (!(val in this.$store.getters)) { + console.error(`[vuex] unknown getter: ${val}`); + return + } + return this.$store.getters[val] + }; + // mark vuex getter for devtools + res[key].vuex = true; + }); + return res +}); + +/** + * Reduce the code which written in Vue.js for dispatch the action + * @param {String} [namespace] - Module's namespace + * @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function as the first param, it can accept anthor params. You can dispatch action and do any other things in this function. specially, You need to pass anthor params from the mapped function. + * @return {Object} + */ +const mapActions = normalizeNamespace((namespace, actions) => { + const res = {}; + normalizeMap(actions).forEach(({ key, val }) => { + res[key] = function mappedAction (...args) { + // get dispatch function from store + let dispatch = this.$store.dispatch; + if (namespace) { + const module = getModuleByNamespace(this.$store, 'mapActions', namespace); + if (!module) { + return + } + dispatch = module.context.dispatch; + } + return typeof val === 'function' + ? val.apply(this, [dispatch].concat(args)) + : dispatch.apply(this.$store, [val].concat(args)) + }; + }); + return res +}); + +/** + * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object + * @param {String} namespace + * @return {Object} + */ +const createNamespacedHelpers = (namespace) => ({ + mapState: mapState.bind(null, namespace), + mapGetters: mapGetters.bind(null, namespace), + mapMutations: mapMutations.bind(null, namespace), + mapActions: mapActions.bind(null, namespace) +}); + +/** + * Normalize the map + * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ] + * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ] + * @param {Array|Object} map + * @return {Object} + */ +function normalizeMap (map) { + return Array.isArray(map) + ? map.map(key => ({ key, val: key })) + : Object.keys(map).map(key => ({ key, val: map[key] })) +} + +/** + * Return a function expect two param contains namespace and map. it will normalize the namespace and then the param's function will handle the new namespace and the map. + * @param {Function} fn + * @return {Function} + */ +function normalizeNamespace (fn) { + return (namespace, map) => { + if (typeof namespace !== 'string') { + map = namespace; + namespace = ''; + } else if (namespace.charAt(namespace.length - 1) !== '/') { + namespace += '/'; + } + return fn(namespace, map) + } +} + +/** + * Search a special module from store by namespace. if module not exist, print error message. + * @param {Object} store + * @param {String} helper + * @param {String} namespace + * @return {Object} + */ +function getModuleByNamespace (store, helper, namespace) { + const module = store._modulesNamespaceMap[namespace]; + if (!module) { + console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`); + } + return module +} + +var index_esm = { + Store, + install, + version: '3.1.1', + mapState, + mapMutations, + mapGetters, + mapActions, + createNamespacedHelpers +}; + +export default index_esm; +export { Store, install, mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers }; diff --git a/dist/vuex.esm.browser.min.js b/dist/vuex.esm.browser.min.js new file mode 100644 index 000000000..1261afbd1 --- /dev/null +++ b/dist/vuex.esm.browser.min.js @@ -0,0 +1,6 @@ +/** + * vuex v3.1.1 + * (c) 2019 Evan You + * @license MIT + */ +const t=("undefined"!=typeof window?window:"undefined"!=typeof global?global:{}).__VUE_DEVTOOLS_GLOBAL_HOOK__;function e(t,e){Object.keys(t).forEach(s=>e(t[s],s))}class s{constructor(t,e){this.runtime=e,this._children=Object.create(null),this._rawModule=t;const s=t.state;this.state=("function"==typeof s?s():s)||{}}get namespaced(){return!!this._rawModule.namespaced}addChild(t,e){this._children[t]=e}removeChild(t){delete this._children[t]}getChild(t){return this._children[t]}update(t){this._rawModule.namespaced=t.namespaced,t.actions&&(this._rawModule.actions=t.actions),t.mutations&&(this._rawModule.mutations=t.mutations),t.getters&&(this._rawModule.getters=t.getters)}forEachChild(t){e(this._children,t)}forEachGetter(t){this._rawModule.getters&&e(this._rawModule.getters,t)}forEachAction(t){this._rawModule.actions&&e(this._rawModule.actions,t)}forEachMutation(t){this._rawModule.mutations&&e(this._rawModule.mutations,t)}}class i{constructor(t){this.register([],t,!1)}get(t){return t.reduce((t,e)=>t.getChild(e),this.root)}getNamespace(t){let e=this.root;return t.reduce((t,s)=>t+((e=e.getChild(s)).namespaced?s+"/":""),"")}update(t){!function t(e,s,i){s.update(i);if(i.modules)for(const o in i.modules){if(!s.getChild(o))return;t(e.concat(o),s.getChild(o),i.modules[o])}}([],this.root,t)}register(t,i,o=!0){const n=new s(i,o);if(0===t.length)this.root=n;else{this.get(t.slice(0,-1)).addChild(t[t.length-1],n)}i.modules&&e(i.modules,(e,s)=>{this.register(t.concat(s),e,o)})}unregister(t){const e=this.get(t.slice(0,-1)),s=t[t.length-1];e.getChild(s).runtime&&e.removeChild(s)}}let o;class n{constructor(e={}){!o&&"undefined"!=typeof window&&window.Vue&&d(window.Vue);const{plugins:s=[],strict:n=!1}=e;this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new i(e),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new o;const r=this,{dispatch:c,commit:h}=this;this.dispatch=function(t,e){return c.call(r,t,e)},this.commit=function(t,e,s){return h.call(r,t,e,s)},this.strict=n;const l=this._modules.root.state;u(this,l,[],this._modules.root),a(this,l),s.forEach(t=>t(this)),(void 0!==e.devtools?e.devtools:o.config.devtools)&&function(e){t&&(e._devtoolHook=t,t.emit("vuex:init",e),t.on("vuex:travel-to-state",t=>{e.replaceState(t)}),e.subscribe((e,s)=>{t.emit("vuex:mutation",e,s)}))}(this)}get state(){return this._vm._data.$$state}set state(t){}commit(t,e,s){const{type:i,payload:o,options:n}=l(t,e,s),r={type:i,payload:o},c=this._mutations[i];c&&(this._withCommit(()=>{c.forEach(function(t){t(o)})}),this._subscribers.forEach(t=>t(r,this.state)))}dispatch(t,e){const{type:s,payload:i}=l(t,e),o={type:s,payload:i},n=this._actions[s];if(n){try{this._actionSubscribers.filter(t=>t.before).forEach(t=>t.before(o,this.state))}catch(t){}return(n.length>1?Promise.all(n.map(t=>t(i))):n[0](i)).then(t=>{try{this._actionSubscribers.filter(t=>t.after).forEach(t=>t.after(o,this.state))}catch(t){}return t})}}subscribe(t){return r(t,this._subscribers)}subscribeAction(t){return r("function"==typeof t?{before:t}:t,this._actionSubscribers)}watch(t,e,s){return this._watcherVM.$watch(()=>t(this.state,this.getters),e,s)}replaceState(t){this._withCommit(()=>{this._vm._data.$$state=t})}registerModule(t,e,s={}){"string"==typeof t&&(t=[t]),this._modules.register(t,e),u(this,this.state,t,this._modules.get(t),s.preserveState),a(this,this.state)}unregisterModule(t){"string"==typeof t&&(t=[t]),this._modules.unregister(t),this._withCommit(()=>{const e=h(this.state,t.slice(0,-1));o.delete(e,t[t.length-1])}),c(this)}hotUpdate(t){this._modules.update(t),c(this,!0)}_withCommit(t){const e=this._committing;this._committing=!0,t(),this._committing=e}}function r(t,e){return e.indexOf(t)<0&&e.push(t),()=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)}}function c(t,e){t._actions=Object.create(null),t._mutations=Object.create(null),t._wrappedGetters=Object.create(null),t._modulesNamespaceMap=Object.create(null);const s=t.state;u(t,s,[],t._modules.root,!0),a(t,s,e)}function a(t,s,i){const n=t._vm;t.getters={};const r=t._wrappedGetters,c={};e(r,(e,s)=>{c[s]=function(t,e){return function(){return t(e)}}(e,t),Object.defineProperty(t.getters,s,{get:()=>t._vm[s],enumerable:!0})});const a=o.config.silent;o.config.silent=!0,t._vm=new o({data:{$$state:s},computed:c}),o.config.silent=a,t.strict&&function(t){t._vm.$watch(function(){return this._data.$$state},()=>{},{deep:!0,sync:!0})}(t),n&&(i&&t._withCommit(()=>{n._data.$$state=null}),o.nextTick(()=>n.$destroy()))}function u(t,e,s,i,n){const r=!s.length,c=t._modules.getNamespace(s);if(i.namespaced&&(t._modulesNamespaceMap[c]=i),!r&&!n){const n=h(e,s.slice(0,-1)),r=s[s.length-1];t._withCommit(()=>{o.set(n,r,i.state)})}const a=i.context=function(t,e,s){const i=""===e,o={dispatch:i?t.dispatch:(s,i,o)=>{const n=l(s,i,o),{payload:r,options:c}=n;let{type:a}=n;return c&&c.root||(a=e+a),t.dispatch(a,r)},commit:i?t.commit:(s,i,o)=>{const n=l(s,i,o),{payload:r,options:c}=n;let{type:a}=n;c&&c.root||(a=e+a),t.commit(a,r,c)}};return Object.defineProperties(o,{getters:{get:i?()=>t.getters:()=>(function(t,e){const s={},i=e.length;return Object.keys(t.getters).forEach(o=>{if(o.slice(0,i)!==e)return;const n=o.slice(i);Object.defineProperty(s,n,{get:()=>t.getters[o],enumerable:!0})}),s})(t,e)},state:{get:()=>h(t.state,s)}}),o}(t,c,s);i.forEachMutation((e,s)=>{!function(t,e,s,i){(t._mutations[e]||(t._mutations[e]=[])).push(function(e){s.call(t,i.state,e)})}(t,c+s,e,a)}),i.forEachAction((e,s)=>{const i=e.root?s:c+s,o=e.handler||e;!function(t,e,s,i){(t._actions[e]||(t._actions[e]=[])).push(function(e,o){let n=s.call(t,{dispatch:i.dispatch,commit:i.commit,getters:i.getters,state:i.state,rootGetters:t.getters,rootState:t.state},e,o);var r;return(r=n)&&"function"==typeof r.then||(n=Promise.resolve(n)),t._devtoolHook?n.catch(e=>{throw t._devtoolHook.emit("vuex:error",e),e}):n})}(t,i,o,a)}),i.forEachGetter((e,s)=>{!function(t,e,s,i){if(t._wrappedGetters[e])return;t._wrappedGetters[e]=function(t){return s(i.state,i.getters,t.state,t.getters)}}(t,c+s,e,a)}),i.forEachChild((i,o)=>{u(t,e,s.concat(o),i,n)})}function h(t,e){return e.length?e.reduce((t,e)=>t[e],t):t}function l(t,e,s){var i;return null!==(i=t)&&"object"==typeof i&&t.type&&(s=e,e=t,t=t.type),{type:t,payload:e,options:s}}function d(t){o&&t===o||function(t){if(Number(t.version.split(".")[0])>=2)t.mixin({beforeCreate:e});else{const s=t.prototype._init;t.prototype._init=function(t={}){t.init=t.init?[e].concat(t.init):e,s.call(this,t)}}function e(){const t=this.$options;t.store?this.$store="function"==typeof t.store?t.store():t.store:t.parent&&t.parent.$store&&(this.$store=t.parent.$store)}}(o=t)}const p=b((t,e)=>{const s={};return y(e).forEach(({key:e,val:i})=>{s[e]=function(){let e=this.$store.state,s=this.$store.getters;if(t){const i=w(this.$store,"mapState",t);if(!i)return;e=i.context.state,s=i.context.getters}return"function"==typeof i?i.call(this,e,s):e[i]},s[e].vuex=!0}),s}),m=b((t,e)=>{const s={};return y(e).forEach(({key:e,val:i})=>{s[e]=function(...e){let s=this.$store.commit;if(t){const e=w(this.$store,"mapMutations",t);if(!e)return;s=e.context.commit}return"function"==typeof i?i.apply(this,[s].concat(e)):s.apply(this.$store,[i].concat(e))}}),s}),f=b((t,e)=>{const s={};return y(e).forEach(({key:e,val:i})=>{i=t+i,s[e]=function(){if(!t||w(this.$store,"mapGetters",t))return this.$store.getters[i]},s[e].vuex=!0}),s}),_=b((t,e)=>{const s={};return y(e).forEach(({key:e,val:i})=>{s[e]=function(...e){let s=this.$store.dispatch;if(t){const e=w(this.$store,"mapActions",t);if(!e)return;s=e.context.dispatch}return"function"==typeof i?i.apply(this,[s].concat(e)):s.apply(this.$store,[i].concat(e))}}),s}),g=t=>({mapState:p.bind(null,t),mapGetters:f.bind(null,t),mapMutations:m.bind(null,t),mapActions:_.bind(null,t)});function y(t){return Array.isArray(t)?t.map(t=>({key:t,val:t})):Object.keys(t).map(e=>({key:e,val:t[e]}))}function b(t){return(e,s)=>("string"!=typeof e?(s=e,e=""):"/"!==e.charAt(e.length-1)&&(e+="/"),t(e,s))}function w(t,e,s){return t._modulesNamespaceMap[s]}export default{Store:n,install:d,version:"3.1.1",mapState:p,mapMutations:m,mapGetters:f,mapActions:_,createNamespacedHelpers:g};export{n as Store,d as install,p as mapState,m as mapMutations,f as mapGetters,_ as mapActions,g as createNamespacedHelpers}; \ No newline at end of file diff --git a/dist/vuex.esm.js b/dist/vuex.esm.js index da94e9b59..982977b7a 100644 --- a/dist/vuex.esm.js +++ b/dist/vuex.esm.js @@ -1,5 +1,5 @@ /** - * vuex v3.1.0 + * vuex v3.1.1 * (c) 2019 Evan You * @license MIT */ @@ -39,9 +39,12 @@ function applyMixin (Vue) { } } -var devtoolHook = - typeof window !== 'undefined' && - window.__VUE_DEVTOOLS_GLOBAL_HOOK__; +var target = typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {}; +var devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__; function devtoolPlugin (store) { if (!devtoolHook) { return } @@ -87,6 +90,12 @@ function assert (condition, msg) { if (!condition) { throw new Error(("[vuex] " + msg)) } } +function partial (fn, arg) { + return function () { + return fn(arg) + } +} + // Base data struct for store's module, package with some attribute and method var Module = function Module (rawModule, runtime) { this.runtime = runtime; @@ -548,7 +557,9 @@ function resetStoreVM (store, state, hot) { var computed = {}; forEachValue(wrappedGetters, function (fn, key) { // use computed to leverage its lazy-caching mechanism - computed[key] = function () { return fn(store); }; + // direct inline function use will lead to closure preserving oldVm. + // using partial to return function with only arguments preserved in closure enviroment. + computed[key] = partial(fn, store); Object.defineProperty(store.getters, key, { get: function () { return store._vm[key]; }, enumerable: true // for local getters @@ -987,7 +998,7 @@ function getModuleByNamespace (store, helper, namespace) { var index_esm = { Store: Store, install: install, - version: '3.1.0', + version: '3.1.1', mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, diff --git a/dist/vuex.js b/dist/vuex.js index bae565303..8ac0fe0dc 100644 --- a/dist/vuex.js +++ b/dist/vuex.js @@ -1,5 +1,5 @@ /** - * vuex v3.1.0 + * vuex v3.1.1 * (c) 2019 Evan You * @license MIT */ @@ -45,9 +45,12 @@ } } - var devtoolHook = - typeof window !== 'undefined' && - window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + var target = typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {}; + var devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__; function devtoolPlugin (store) { if (!devtoolHook) { return } @@ -93,6 +96,12 @@ if (!condition) { throw new Error(("[vuex] " + msg)) } } + function partial (fn, arg) { + return function () { + return fn(arg) + } + } + // Base data struct for store's module, package with some attribute and method var Module = function Module (rawModule, runtime) { this.runtime = runtime; @@ -553,7 +562,9 @@ var computed = {}; forEachValue(wrappedGetters, function (fn, key) { // use computed to leverage its lazy-caching mechanism - computed[key] = function () { return fn(store); }; + // direct inline function use will lead to closure preserving oldVm. + // using partial to return function with only arguments preserved in closure enviroment. + computed[key] = partial(fn, store); Object.defineProperty(store.getters, key, { get: function () { return store._vm[key]; }, enumerable: true // for local getters @@ -992,7 +1003,7 @@ var index = { Store: Store, install: install, - version: '3.1.0', + version: '3.1.1', mapState: mapState, mapMutations: mapMutations, mapGetters: mapGetters, diff --git a/dist/vuex.min.js b/dist/vuex.min.js index 15b9039cc..787aee830 100644 --- a/dist/vuex.min.js +++ b/dist/vuex.min.js @@ -1,6 +1,6 @@ /** - * vuex v3.1.0 + * vuex v3.1.1 * (c) 2019 Evan You * @license MIT */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Vuex=e()}(this,function(){"use strict";var u="undefined"!=typeof window&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function a(e,n){Object.keys(e).forEach(function(t){return n(e[t],t)})}var r=function(t,e){this.runtime=e,this._children=Object.create(null);var n=(this._rawModule=t).state;this.state=("function"==typeof n?n():n)||{}},t={namespaced:{configurable:!0}};t.namespaced.get=function(){return!!this._rawModule.namespaced},r.prototype.addChild=function(t,e){this._children[t]=e},r.prototype.removeChild=function(t){delete this._children[t]},r.prototype.getChild=function(t){return this._children[t]},r.prototype.update=function(t){this._rawModule.namespaced=t.namespaced,t.actions&&(this._rawModule.actions=t.actions),t.mutations&&(this._rawModule.mutations=t.mutations),t.getters&&(this._rawModule.getters=t.getters)},r.prototype.forEachChild=function(t){a(this._children,t)},r.prototype.forEachGetter=function(t){this._rawModule.getters&&a(this._rawModule.getters,t)},r.prototype.forEachAction=function(t){this._rawModule.actions&&a(this._rawModule.actions,t)},r.prototype.forEachMutation=function(t){this._rawModule.mutations&&a(this._rawModule.mutations,t)},Object.defineProperties(r.prototype,t);var m,f=function(t){this.register([],t,!1)};f.prototype.get=function(t){return t.reduce(function(t,e){return t.getChild(e)},this.root)},f.prototype.getNamespace=function(t){var n=this.root;return t.reduce(function(t,e){return t+((n=n.getChild(e)).namespaced?e+"/":"")},"")},f.prototype.update=function(t){!function t(e,n,o){n.update(o);if(o.modules)for(var i in o.modules){if(!n.getChild(i))return;t(e.concat(i),n.getChild(i),o.modules[i])}}([],this.root,t)},f.prototype.register=function(n,t,o){var i=this;void 0===o&&(o=!0);var e=new r(t,o);0===n.length?this.root=e:this.get(n.slice(0,-1)).addChild(n[n.length-1],e);t.modules&&a(t.modules,function(t,e){i.register(n.concat(e),t,o)})},f.prototype.unregister=function(t){var e=this.get(t.slice(0,-1)),n=t[t.length-1];e.getChild(n).runtime&&e.removeChild(n)};var e=function(t){var e=this;void 0===t&&(t={}),!m&&"undefined"!=typeof window&&window.Vue&&p(window.Vue);var n=t.plugins;void 0===n&&(n=[]);var o=t.strict;void 0===o&&(o=!1),this._committing=!1,this._actions=Object.create(null),this._actionSubscribers=[],this._mutations=Object.create(null),this._wrappedGetters=Object.create(null),this._modules=new f(t),this._modulesNamespaceMap=Object.create(null),this._subscribers=[],this._watcherVM=new m;var i=this,r=this.dispatch,s=this.commit;this.dispatch=function(t,e){return r.call(i,t,e)},this.commit=function(t,e,n){return s.call(i,t,e,n)},this.strict=o;var a,c=this._modules.root.state;v(this,c,[],this._modules.root),h(this,c),n.forEach(function(t){return t(e)}),(void 0!==t.devtools?t.devtools:m.config.devtools)&&(a=this,u&&((a._devtoolHook=u).emit("vuex:init",a),u.on("vuex:travel-to-state",function(t){a.replaceState(t)}),a.subscribe(function(t,e){u.emit("vuex:mutation",t,e)})))},n={state:{configurable:!0}};function o(e,n){return n.indexOf(e)<0&&n.push(e),function(){var t=n.indexOf(e);-1-1&&e.splice(n,1)}}function u(t,e){t._actions=Object.create(null),t._mutations=Object.create(null),t._wrappedGetters=Object.create(null),t._modulesNamespaceMap=Object.create(null);var n=t.state;p(t,n,[],t._modules.root,!0),f(t,n,e)}function f(t,n,o){var r=t._vm;t.getters={};var s=t._wrappedGetters,a={};e(s,function(e,n){a[n]=function(t,e){return function(){return t(e)}}(e,t),Object.defineProperty(t.getters,n,{get:function(){return t._vm[n]},enumerable:!0})});var c=i.config.silent;i.config.silent=!0,t._vm=new i({data:{$$state:n},computed:a}),i.config.silent=c,t.strict&&function(t){t._vm.$watch(function(){return this._data.$$state},function(){},{deep:!0,sync:!0})}(t),r&&(o&&t._withCommit(function(){r._data.$$state=null}),i.nextTick(function(){return r.$destroy()}))}function p(t,e,n,o,r){var s=!n.length,a=t._modules.getNamespace(n);if(o.namespaced&&(t._modulesNamespaceMap[a]=o),!s&&!r){var c=h(e,n.slice(0,-1)),u=n[n.length-1];t._withCommit(function(){i.set(c,u,o.state)})}var f=o.context=function(t,e,n){var o=""===e,i={dispatch:o?t.dispatch:function(n,o,i){var r=l(n,o,i),s=r.payload,a=r.options,c=r.type;return a&&a.root||(c=e+c),t.dispatch(c,s)},commit:o?t.commit:function(n,o,i){var r=l(n,o,i),s=r.payload,a=r.options,c=r.type;a&&a.root||(c=e+c),t.commit(c,s,a)}};return Object.defineProperties(i,{getters:{get:o?function(){return t.getters}:function(){return function(t,e){var n={},o=e.length;return Object.keys(t.getters).forEach(function(i){if(i.slice(0,o)===e){var r=i.slice(o);Object.defineProperty(n,r,{get:function(){return t.getters[i]},enumerable:!0})}}),n}(t,e)}},state:{get:function(){return h(t.state,n)}}}),i}(t,a,n);o.forEachMutation(function(e,n){!function(t,e,n,o){(t._mutations[e]||(t._mutations[e]=[])).push(function(e){n.call(t,o.state,e)})}(t,a+n,e,f)}),o.forEachAction(function(e,n){var o=e.root?n:a+n,i=e.handler||e;!function(t,e,n,o){(t._actions[e]||(t._actions[e]=[])).push(function(e,i){var r,s=n.call(t,{dispatch:o.dispatch,commit:o.commit,getters:o.getters,state:o.state,rootGetters:t.getters,rootState:t.state},e,i);return(r=s)&&"function"==typeof r.then||(s=Promise.resolve(s)),t._devtoolHook?s.catch(function(e){throw t._devtoolHook.emit("vuex:error",e),e}):s})}(t,o,i,f)}),o.forEachGetter(function(e,n){!function(t,e,n,o){if(t._wrappedGetters[e])return;t._wrappedGetters[e]=function(t){return n(o.state,o.getters,t.state,t.getters)}}(t,a+n,e,f)}),o.forEachChild(function(o,i){p(t,e,n.concat(i),o,r)})}function h(t,e){return e.length?e.reduce(function(t,e){return t[e]},t):t}function l(t,e,n){var o;return null!==(o=t)&&"object"==typeof o&&t.type&&(n=e,e=t,t=t.type),{type:t,payload:e,options:n}}function d(t){i&&t===i||function(t){if(Number(t.version.split(".")[0])>=2)t.mixin({beforeCreate:n});else{var e=t.prototype._init;t.prototype._init=function(t){void 0===t&&(t={}),t.init=t.init?[n].concat(t.init):n,e.call(this,t)}}function n(){var t=this.$options;t.store?this.$store="function"==typeof t.store?t.store():t.store:t.parent&&t.parent.$store&&(this.$store=t.parent.$store)}}(i=t)}a.state.get=function(){return this._vm._data.$$state},a.state.set=function(t){},s.prototype.commit=function(t,e,n){var o=this,i=l(t,e,n),r=i.type,s=i.payload,a={type:r,payload:s},c=this._mutations[r];c&&(this._withCommit(function(){c.forEach(function(t){t(s)})}),this._subscribers.forEach(function(t){return t(a,o.state)}))},s.prototype.dispatch=function(t,e){var n=this,o=l(t,e),i=o.type,r=o.payload,s={type:i,payload:r},a=this._actions[i];if(a){try{this._actionSubscribers.filter(function(t){return t.before}).forEach(function(t){return t.before(s,n.state)})}catch(t){}return(a.length>1?Promise.all(a.map(function(t){return t(r)})):a[0](r)).then(function(t){try{n._actionSubscribers.filter(function(t){return t.after}).forEach(function(t){return t.after(s,n.state)})}catch(t){}return t})}},s.prototype.subscribe=function(t){return c(t,this._subscribers)},s.prototype.subscribeAction=function(t){return c("function"==typeof t?{before:t}:t,this._actionSubscribers)},s.prototype.watch=function(t,e,n){var o=this;return this._watcherVM.$watch(function(){return t(o.state,o.getters)},e,n)},s.prototype.replaceState=function(t){var e=this;this._withCommit(function(){e._vm._data.$$state=t})},s.prototype.registerModule=function(t,e,n){void 0===n&&(n={}),"string"==typeof t&&(t=[t]),this._modules.register(t,e),p(this,this.state,t,this._modules.get(t),n.preserveState),f(this,this.state)},s.prototype.unregisterModule=function(t){var e=this;"string"==typeof t&&(t=[t]),this._modules.unregister(t),this._withCommit(function(){var n=h(e.state,t.slice(0,-1));i.delete(n,t[t.length-1])}),u(this)},s.prototype.hotUpdate=function(t){this._modules.update(t),u(this,!0)},s.prototype._withCommit=function(t){var e=this._committing;this._committing=!0,t(),this._committing=e},Object.defineProperties(s.prototype,a);var m=b(function(t,e){var n={};return g(e).forEach(function(e){var o=e.key,i=e.val;n[o]=function(){var e=this.$store.state,n=this.$store.getters;if(t){var o=w(this.$store,"mapState",t);if(!o)return;e=o.context.state,n=o.context.getters}return"function"==typeof i?i.call(this,e,n):e[i]},n[o].vuex=!0}),n}),v=b(function(t,e){var n={};return g(e).forEach(function(e){var o=e.key,i=e.val;n[o]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var o=this.$store.commit;if(t){var r=w(this.$store,"mapMutations",t);if(!r)return;o=r.context.commit}return"function"==typeof i?i.apply(this,[o].concat(e)):o.apply(this.$store,[i].concat(e))}}),n}),_=b(function(t,e){var n={};return g(e).forEach(function(e){var o=e.key,i=e.val;i=t+i,n[o]=function(){if(!t||w(this.$store,"mapGetters",t))return this.$store.getters[i]},n[o].vuex=!0}),n}),y=b(function(t,e){var n={};return g(e).forEach(function(e){var o=e.key,i=e.val;n[o]=function(){for(var e=[],n=arguments.length;n--;)e[n]=arguments[n];var o=this.$store.dispatch;if(t){var r=w(this.$store,"mapActions",t);if(!r)return;o=r.context.dispatch}return"function"==typeof i?i.apply(this,[o].concat(e)):o.apply(this.$store,[i].concat(e))}}),n});function g(t){return Array.isArray(t)?t.map(function(t){return{key:t,val:t}}):Object.keys(t).map(function(e){return{key:e,val:t[e]}})}function b(t){return function(e,n){return"string"!=typeof e?(n=e,e=""):"/"!==e.charAt(e.length-1)&&(e+="/"),t(e,n)}}function w(t,e,n){return t._modulesNamespaceMap[n]}return{Store:s,install:d,version:"3.1.1",mapState:m,mapMutations:v,mapGetters:_,mapActions:y,createNamespacedHelpers:function(t){return{mapState:m.bind(null,t),mapGetters:_.bind(null,t),mapMutations:v.bind(null,t),mapActions:y.bind(null,t)}}}}); \ No newline at end of file diff --git a/docs-gitbook/pt-br/getting-started.md b/docs-gitbook/pt-br/getting-started.md index 161937f78..8b3917833 100644 --- a/docs-gitbook/pt-br/getting-started.md +++ b/docs-gitbook/pt-br/getting-started.md @@ -34,7 +34,7 @@ store.commit('increment') console.log(store.state.count) // -> 1 ``` -Novamente, a razão pela qual estamos fazendo _commit_ de uma mutação em vez de mudar `store.state.count` diretamente, é porque queremos rastreá-la explicitamente. Esta convenção simples torna sua intenção mais explícita, de modo que você possa ter melhores argumetos sobre as mudanças de estado em seu aplicativo ao ler o código. Além disso, isso nos dá a oportunidade de implementar ferramentas que podem registrar cada mutação, tirar _snapshots_ de estado ou mesmo realizar depuração viajando pelo histórico de estado (_time travel_). +Novamente, a razão pela qual estamos fazendo _commit_ de uma mutação em vez de mudar `store.state.count` diretamente, é porque queremos rastreá-la explicitamente. Esta convenção simples torna sua intenção mais explícita, de modo que você possa ter melhores argumentos sobre as mudanças de estado em seu aplicativo ao ler o código. Além disso, isso nos dá a oportunidade de implementar ferramentas que podem registrar cada mutação, tirar _snapshots_ de estado ou mesmo realizar depuração viajando pelo histórico de estado (_time travel_). Usar o estado do _store_ em um componente simplesmente envolve o retorno do estado dentro de um dado computado, porque o estado do _store_ é reativo. Fazer alterações simplesmente significa fazer _commit_ de mutações nos métodos dos componentes. diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 98e4b79d5..c67d10273 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -39,6 +39,10 @@ module.exports = { serviceWorker: true, theme: 'vue', themeConfig: { + algolia: { + apiKey: '97f135e4b5f5487fb53f0f2dae8db59d', + indexName: 'vuex', + }, repo: 'vuejs/vuex', docsDir: 'docs', locales: { diff --git a/docs/.vuepress/override.styl b/docs/.vuepress/override.styl new file mode 100644 index 000000000..7222c4be1 --- /dev/null +++ b/docs/.vuepress/override.styl @@ -0,0 +1,32 @@ +.scrimba + background-color #e7ecf3 + padding 1em 1.25em + border-radius 2px + color #486491 + position relative + margin-top: 16px + a + color #486491 !important + position relative + padding-left 36px + &:before + content '' + position absolute + display block + width 30px + height 30px + top -5px + left -4px + border-radius 50% + background-color #73abfe + &:after + content '' + position absolute + display block + width 0 + height 0 + top 5px + left 8px + border-top 5px solid transparent + border-bottom 5px solid transparent + border-left 8px solid #fff \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 3cddf78d3..905874b95 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,17 +31,17 @@ new Vue({ It is a self-contained app with the following parts: -- The **state**, which is the source of truth that drives our app; -- The **view**, which is just a declarative mapping of the **state**; -- The **actions**, which are the possible ways the state could change in reaction to user inputs from the **view**. +- The **state**, the source of truth that drives our app; +- The **view**, a declarative mapping of the **state**; +- The **actions**, the possible ways the state could change in reaction to user inputs from the **view**. -This is an extremely simple representation of the concept of "one-way data flow": +This is an simple representation of the concept of "one-way data flow":

-However, the simplicity quickly breaks down when we have **multiple components that share common state**: +However, the simplicity quickly breaks down when we have **multiple components that share a common state**: - Multiple views may depend on the same piece of state. - Actions from different views may need to mutate the same piece of state. @@ -50,15 +50,17 @@ For problem one, passing props can be tedious for deeply nested components, and So why don't we extract the shared state out of the components, and manage it in a global singleton? With this, our component tree becomes a big "view", and any component can access the state or trigger actions, no matter where they are in the tree! -In addition, by defining and separating the concepts involved in state management and enforcing certain rules, we also give our code more structure and maintainability. +By defining and separating the concepts involved in state management and enforcing rules that maintain independece between views and states, we give our code more structure and maintainability. This is the basic idea behind Vuex, inspired by [Flux](https://facebook.github.io/flux/docs/overview.html), [Redux](http://redux.js.org/) and [The Elm Architecture](https://guide.elm-lang.org/architecture/). Unlike the other patterns, Vuex is also a library implementation tailored specifically for Vue.js to take advantage of its granular reactivity system for efficient updates. +If you want to learn Vuex in an interactive way you can check out this [Vuex course on Scrimba](https://scrimba.com/g/gvuex), which gives you a mix of screencast and code playground that you can pause and play around with anytime. + ![vuex](/vuex.png) ### When Should I Use It? -Although Vuex helps us deal with shared state management, it also comes with the cost of more concepts and boilerplate. It's a trade-off between short term and long term productivity. +Vuex helps us deal with shared state management with the cost of more concepts and boilerplate. It's a trade-off between short term and long term productivity. If you've never built a large-scale SPA and jump right into Vuex, it may feel verbose and daunting. That's perfectly normal - if your app is simple, you will most likely be fine without Vuex. A simple [store pattern](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch) may be all you need. But if you are building a medium-to-large-scale SPA, chances are you have run into situations that make you think about how to better handle state outside of your Vue components, and Vuex will be the natural next step for you. There's a good quote from Dan Abramov, the author of Redux: diff --git a/docs/api/README.md b/docs/api/README.md index e28ce1921..c859f876c 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -169,7 +169,7 @@ const store = new Vuex.Store({ ...options }) - `watch(fn: Function, callback: Function, options?: Object): Function` - Reactively watch `fn`'s return value, and call the callback when the value changes. `fn` receives the store's state as the first argument, and getters as the second argument. Accepts an optional options object that takes the same options as [Vue's `vm.$watch` method](https://vuejs.org/v2/api/#watch). + Reactively watch `fn`'s return value, and call the callback when the value changes. `fn` receives the store's state as the first argument, and getters as the second argument. Accepts an optional options object that takes the same options as [Vue's `vm.$watch` method](https://vuejs.org/v2/api/#vm-watch). To stop watching, call the returned unwatch function. diff --git a/docs/fr/README.md b/docs/fr/README.md index 7ff74cb57..21aedb2bc 100644 --- a/docs/fr/README.md +++ b/docs/fr/README.md @@ -52,6 +52,8 @@ De plus, en définissant et en séparant les concepts impliqués dans la gestion Voilà l'idée de base derrière Vuex, inspiré par [Flux](https://facebook.github.io/flux/docs/overview.html), [Redux](http://redux.js.org/) et [l'architecture Elm](https://guide.elm-lang.org/architecture/). À l'inverse des autres modèles, Vuex est aussi une bibliothèque d'implémentation conçue spécialement pour Vue.js afin de bénéficier de son système de réactivité granulaire pour des modifications efficaces. +Si vous voulez apprendre Vuex de manière interactive, jetez un oeil à ce [cours sur Vuex sur Scrimba.](https://scrimba.com/g/gvuex) + ![vuex](/vuex.png) ### Quand l'utiliser ? diff --git a/docs/fr/guide/README.md b/docs/fr/guide/README.md index 05c32dc2f..ce251fd86 100644 --- a/docs/fr/guide/README.md +++ b/docs/fr/guide/README.md @@ -1,5 +1,7 @@ # Pour commencer +
Essayez cette partie sur Scrimba (EN)
+ Au cœur de chaque application Vuex, il y a la **zone de stockage (« store »)**. Un « store » est tout simplement un conteneur avec l'**état (« state »)** de votre application. Il y a deux choses qui différencient un store Vuex d'un simple objet global : 1. Les stores Vuex sont réactifs. Quand les composants Vue y récupèrent l'état, ils se mettront à jour de façon réactive et efficace si l'état du store a changé. diff --git a/docs/fr/guide/actions.md b/docs/fr/guide/actions.md index ebeacc803..2cc82bdd2 100644 --- a/docs/fr/guide/actions.md +++ b/docs/fr/guide/actions.md @@ -1,5 +1,7 @@ # Actions +
Essayez cette partie sur Scrimba (EN)
+ Les actions sont similaires aux mutations, à la différence que : - Au lieu de modifier l'état, les actions actent des mutations. diff --git a/docs/fr/guide/forms.md b/docs/fr/guide/forms.md index f304def1c..3d8dfef5e 100644 --- a/docs/fr/guide/forms.md +++ b/docs/fr/guide/forms.md @@ -1,5 +1,7 @@ # Gestion des formulaires +
Essayez cette partie sur Scrimba (EN)
+ Lorsque l'on utilise Vuex en mode strict, il peut être compliqué d'utiliser `v-model` sur une partie de l'état qui appartient à Vuex : ``` html diff --git a/docs/fr/guide/getters.md b/docs/fr/guide/getters.md index b6d6eed7c..1b70f8972 100644 --- a/docs/fr/guide/getters.md +++ b/docs/fr/guide/getters.md @@ -1,5 +1,7 @@ # Accesseurs +
Essayez cette partie sur Scrimba (EN)
+ Parfois nous avons besoin de calculer des valeurs basées sur l'état du store, par exemple pour filtrer une liste d'éléments et les compter : ``` js diff --git a/docs/fr/guide/modules.md b/docs/fr/guide/modules.md index 39eb9edc8..8c51472fa 100644 --- a/docs/fr/guide/modules.md +++ b/docs/fr/guide/modules.md @@ -1,5 +1,7 @@ # Modules +
Essayez cette partie sur Scrimba (EN)
+ Du fait de l'utilisation d'un arbre d'état unique, tout l'état de notre application est contenu dans un seul et même gros objet. Cependant, au fur et à mesure que notre application grandit, le store peut devenir très engorgé. Pour y remédier, Vuex nous permet de diviser notre store en **modules**. Chaque module peut contenir ses propres états, mutations, actions, accesseurs. Il peut même contenir ses propres modules internes. diff --git a/docs/fr/guide/mutations.md b/docs/fr/guide/mutations.md index 3b867d250..edc1f8261 100644 --- a/docs/fr/guide/mutations.md +++ b/docs/fr/guide/mutations.md @@ -1,5 +1,7 @@ # Mutations +
Essayez cette partie sur Scrimba (EN)
+ La seule façon de vraiment modifier l'état dans un store Vuex est d'acter une mutation. Les mutations Vuex sont très similaires aux évènements : chaque mutation a un **type** sous forme de chaine de caractères et un **gestionnaire**. La fonction de gestion est en charge de procéder aux véritables modifications de l'état, et elle reçoit l'état en premier argument : ``` js diff --git a/docs/fr/guide/plugins.md b/docs/fr/guide/plugins.md index 30aab77c0..56df03a3a 100644 --- a/docs/fr/guide/plugins.md +++ b/docs/fr/guide/plugins.md @@ -1,5 +1,7 @@ # Plugins +
Essayez cette partie sur Scrimba (EN)
+ Les stores Vuex prennent une option `plugins` qui expose des hooks pour chaque mutation. Un plugin Vuex est simplement une fonction qui reçoit un store comme unique argument : ``` js diff --git a/docs/fr/guide/state.md b/docs/fr/guide/state.md index 59ce19b02..84ad0f44e 100644 --- a/docs/fr/guide/state.md +++ b/docs/fr/guide/state.md @@ -2,6 +2,8 @@ ### Arbre d'état unique +
Essayez cette partie sur Scrimba (EN)
+ Vuex utilise un **arbre d'état unique**, c'est-à-dire que cet unique objet contient tout l'état au niveau applicatif et sert de « source de vérité unique ». Cela signifie également que vous n'aurez qu'un seul store pour chaque application. Un arbre d'état unique rend rapide la localisation d'une partie spécifique de l'état et permet de facilement prendre des instantanés de l'état actuel de l'application à des fins de débogage. L'arbre d'état unique n'entre pas en conflit avec la modularité. Dans les prochains chapitres, nous examinerons comment séparer votre état et vos mutations dans des sous-modules. @@ -58,6 +60,8 @@ const Counter = { ### La fonction utilitaire `mapState` +
Essayez cette partie sur Scrimba (EN)
+ Lorsqu'un composant a besoin d'utiliser plusieurs accesseurs ou propriétés de l'état du store, déclarer toutes ces propriétés calculées peut devenir répétitif et verbeux. Afin de pallier à ça, nous pouvons utiliser la fonction utilitaire `mapState` qui génère des fonctions d'accession pour nous et nous épargne quelques coups de clavier : ``` js @@ -92,7 +96,7 @@ computed: mapState([ ### Opérateur de décomposition -Notez que `mapState` renvoie un objet. Comment l'utiliser en complément des autres propriétés calculées locales ? Normalement, il faudrait utiliser un outil pour fusionner les multiples objets en un seul afin de passer cet objet final à `computed`. Cependant avec l'[opérateur de décomposition](https://github.com/sebmarkbage/ecmascript-rest-spread) (qui est une proposition stage-4 ECMASCript), nous pouvons grandement simplifier la syntaxe : +Notez que `mapState` renvoie un objet. Comment l'utiliser en complément des autres propriétés calculées locales ? Normalement, il faudrait utiliser un outil pour fusionner les multiples objets en un seul afin de passer cet objet final à `computed`. Cependant avec l'[opérateur de décomposition](https://github.com/sebmarkbage/ecmascript-rest-spread) (qui est une proposition stage-4 ECMAScript), nous pouvons grandement simplifier la syntaxe : ``` js computed: { diff --git a/docs/fr/guide/testing.md b/docs/fr/guide/testing.md index f0cae2b10..9682e8ba9 100644 --- a/docs/fr/guide/testing.md +++ b/docs/fr/guide/testing.md @@ -1,5 +1,7 @@ # Tests +
Essayez cette partie sur Scrimba (EN)
+ Les parties principales que l'on veut couvrir par des tests unitaires avec Vuex sont les mutations et les actions. ### Tester les mutations diff --git a/docs/guide/README.md b/docs/guide/README.md index 496c665f7..45882a5c3 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -1,5 +1,7 @@ # Getting Started +
Try this lesson on Scrimba
+ At the center of every Vuex application is the **store**. A "store" is basically a container that holds your application **state**. There are two things that make a Vuex store different from a plain global object: 1. Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state changes. diff --git a/docs/guide/actions.md b/docs/guide/actions.md index b022332e2..081141ab8 100644 --- a/docs/guide/actions.md +++ b/docs/guide/actions.md @@ -1,5 +1,7 @@ # Actions +
Try this lesson on Scrimba
+ Actions are similar to mutations, the differences being that: - Instead of mutating the state, actions commit mutations. @@ -45,7 +47,7 @@ Actions are triggered with the `store.dispatch` method: store.dispatch('increment') ``` -This may look dumb at first sight: if we want to increment the count, why don't we just call `store.commit('increment')` directly? Remember that **mutations have to be synchronous**? Actions don't. We can perform **asynchronous** operations inside an action: +This may look silly at first sight: if we want to increment the count, why don't we just call `store.commit('increment')` directly? Remember that **mutations have to be synchronous**? Actions don't. We can perform **asynchronous** operations inside an action: ``` js actions: { diff --git a/docs/guide/forms.md b/docs/guide/forms.md index 37fd58e56..d9d4d586f 100644 --- a/docs/guide/forms.md +++ b/docs/guide/forms.md @@ -1,5 +1,7 @@ # Form Handling +
Try this lesson on Scrimba
+ When using Vuex in strict mode, it could be a bit tricky to use `v-model` on a piece of state that belongs to Vuex: ``` html diff --git a/docs/guide/getters.md b/docs/guide/getters.md index 124f38fac..981901efc 100644 --- a/docs/guide/getters.md +++ b/docs/guide/getters.md @@ -1,5 +1,7 @@ # Getters +
Try this lesson on Scrimba
+ Sometimes we may need to compute derived state based on store state, for example filtering through a list of items and counting them: ``` js diff --git a/docs/guide/modules.md b/docs/guide/modules.md index ac0853621..8715ce748 100644 --- a/docs/guide/modules.md +++ b/docs/guide/modules.md @@ -1,5 +1,7 @@ # Modules +
Try this lesson on Scrimba
+ Due to using a single state tree, all state of our application is contained inside one big object. However, as our application grows in scale, the store can get really bloated. To help with that, Vuex allows us to divide our store into **modules**. Each module can contain its own state, mutations, actions, getters, and even nested modules - it's fractal all the way down: @@ -295,8 +297,12 @@ Dynamic module registration makes it possible for other Vue plugins to also leve You can also remove a dynamically registered module with `store.unregisterModule(moduleName)`. Note you cannot remove static modules (declared at store creation) with this method. +#### Preserving state + It may be likely that you want to preserve the previous state when registering a new module, such as preserving state from a Server Side Rendered app. You can achieve this with `preserveState` option: `store.registerModule('a', module, { preserveState: true })` +When you set `preserveState: true`, the module is registered, actions, mutations and getters are added to the store, but the state not. It's assumed that your store state already contains state for that module and you don't want to overwrite it. + ### Module Reuse Sometimes we may need to create multiple instances of a module, for example: diff --git a/docs/guide/mutations.md b/docs/guide/mutations.md index 965d0c62c..8c206529c 100644 --- a/docs/guide/mutations.md +++ b/docs/guide/mutations.md @@ -1,5 +1,7 @@ # Mutations +
Try this lesson on Scrimba
+ The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string **type** and a **handler**. The handler function is where we perform actual state modifications, and it will receive the state as the first argument: ``` js diff --git a/docs/guide/plugins.md b/docs/guide/plugins.md index 22f937aa8..fe4939c0a 100644 --- a/docs/guide/plugins.md +++ b/docs/guide/plugins.md @@ -1,5 +1,7 @@ # Plugins +
Try this lesson on Scrimba
+ Vuex stores accept the `plugins` option that exposes hooks for each mutation. A Vuex plugin is simply a function that receives the store as the only argument: ``` js diff --git a/docs/guide/state.md b/docs/guide/state.md index 40d477b08..b89c39bc9 100644 --- a/docs/guide/state.md +++ b/docs/guide/state.md @@ -2,6 +2,8 @@ ### Single State Tree +
Try this lesson on Scrimba
+ Vuex uses a **single state tree** - that is, this single object contains all your application level state and serves as the "single source of truth". This also means usually you will have only one store for each application. A single state tree makes it straightforward to locate a specific piece of state, and allows us to easily take snapshots of the current app state for debugging purposes. The single state tree does not conflict with modularity - in later chapters we will discuss how to split your state and mutations into sub modules. @@ -58,6 +60,8 @@ const Counter = { ### The `mapState` Helper +
Try this lesson on Scrimba
+ When a component needs to make use of multiple store state properties or getters, declaring all these computed properties can get repetitive and verbose. To deal with this we can make use of the `mapState` helper which generates computed getter functions for us, saving us some keystrokes: ``` js diff --git a/docs/guide/testing.md b/docs/guide/testing.md index 63b9d9120..3ca0bdf2d 100644 --- a/docs/guide/testing.md +++ b/docs/guide/testing.md @@ -1,5 +1,7 @@ # Testing +
Try this lesson on Scrimba
+ The main parts we want to unit test in Vuex are mutations and actions. ### Testing Mutations diff --git a/docs/ja/README.md b/docs/ja/README.md index f1b0e21ba..086217eb1 100644 --- a/docs/ja/README.md +++ b/docs/ja/README.md @@ -1,5 +1,7 @@ # Vuex とは何か? + + Vuex は Vue.js アプリケーションのための **状態管理パターン + ライブラリ**です。 これは予測可能な方法によってのみ状態の変異を行うというルールを保証し、アプリケーション内の全てのコンポーネントのための集中型のストアとして機能します。 また Vue 公式の[開発ツール拡張](https://github.com/vuejs/vue-devtools)と連携し、設定なしでタイムトラベルデバッグやステートのスナップショットのエクスポートやインポートのような高度な機能を提供します。 @@ -55,6 +57,8 @@ new Vue({ これが Vuex の背景にある基本的なアイディアであり、[Flux](https://facebook.github.io/flux/docs/overview.html)、 [Redux](http://redux.js.org/) そして [The Elm Architecture](https://guide.elm-lang.org/architecture/)から影響を受けています。 他のパターンと異なるのは、Vuex は効率的な更新のために、Vue.js の粒度の細かいリアクティビティシステムを利用するよう特別に調整して実装されたライブラリだということです。 +あなたがもし対話型の方法でVuexを学びたいのであれば、[Scrimba](https://scrimba.com/g/gvuex)のVuexコースをぜひ試してみてください。 + ![vuex](/vuex.png) ### いつ、Vuexを使うべきでしょうか? diff --git a/docs/ja/api/README.md b/docs/ja/api/README.md index 250363b49..43afe97c7 100644 --- a/docs/ja/api/README.md +++ b/docs/ja/api/README.md @@ -116,6 +116,19 @@ const store = new Vuex.Store({ ...options }) [詳細](../guide/strict.md) + +### devtools + + - 型: `Boolean` + + 特定の Vuex インスタンスに対して開発ツールをオン、またはオフにします。インスタンスに false を渡すと、開発ツールのプラグインを購読しないように Vuex ストアに伝えます。1 ページに複数のストアがある場合に便利です。 + + ``` js + { + devtools: false + } + ``` + ## Vuex.Store インスタンスプロパティ ### state @@ -156,7 +169,7 @@ const store = new Vuex.Store({ ...options }) - **`watch(fn: Function, callback: Function, options?: Object): Function`** - `fn`が返す値をリアクティブに監視し、値が変わった時にコールバックを呼びます。`fn`は最初の引数としてストアのステートを、2番目の引数としてゲッターを受け取ります。 Vue の`vm.$watch`メソッドと同じオプションをオプションのオブジェクトとして受け付けます。 + `fn`が返す値をリアクティブに監視し、値が変わった時にコールバックを呼びます。`fn`は最初の引数としてストアのステートを、2番目の引数としてゲッターを受け取ります。 [Vue の`vm.$watch`メソッド](https://jp.vuejs.org/v2/api/#watch)と同じオプションをオプションのオブジェクトとして受け付けます。 監視を止める場合は、返された unwatch 関数を呼び出します。 @@ -192,7 +205,24 @@ const store = new Vuex.Store({ ...options }) }) ``` - プラグインで最も一般的に使用されます。[Details](../guide/plugins.md) + 購読を停止するには、返された購読解除関数を呼びます。 + + > 3.1.0 で新規追加 + + 3.1.0 から、`subscribeAction` は購読ハンドラがアクションディスパッチの*前 (before)*、または*後 (after)*に呼びだすべきかどうか(デフォルトの動作は、*before* です)指定することもできます。 + + ``` js + store.subscribeAction({ + before: (action, state) => { + console.log(`before action ${action.type}`) + }, + after: (action, state) => { + console.log(`after action ${action.type}`) + } + }) + ``` + + プラグインで最も一般的に使用されます。[詳細](../guide/plugins.md) ### registerModule diff --git a/docs/ja/guide/README.md b/docs/ja/guide/README.md index 7b7373280..d237dcc58 100644 --- a/docs/ja/guide/README.md +++ b/docs/ja/guide/README.md @@ -1,5 +1,7 @@ # Vuex 入門 + + Vuex アプリケーションの中心にあるものは**ストア**です。"ストア" は、基本的にアプリケーションの **状態(state)** を保持するコンテナです。単純なグローバルオブジェクトとの違いが 2つあります。 1. Vuex ストアはリアクティブです。Vue コンポーネントがストアから状態を取り出すとき、もしストアの状態が変化したら、ストアはリアクティブかつ効率的に更新を行います。 diff --git a/docs/ja/guide/actions.md b/docs/ja/guide/actions.md index 5ef205768..2439b42c5 100644 --- a/docs/ja/guide/actions.md +++ b/docs/ja/guide/actions.md @@ -1,5 +1,7 @@ # アクション + + アクションはミューテーションと似ていますが、下記の点で異なります: - アクションは、状態を変更するのではなく、ミューテーションをコミットします。 @@ -25,7 +27,7 @@ const store = new Vuex.Store({ }) ``` -アクションハンドラはストアインスタンスのメソッドやプロパティのセットと同じものを呼び出せるコンテキストオブジェクトを受け取ります。したがって `context.commit` を呼び出すことでミューテーションをコミットできます。あるいは `context.state` や `context.getters` で、状態やゲッターにアクセスできます。なぜコンテキストオブジェクトがストアインスタンスそのものではないのかは、後ほど[モジュール](modules.md)で説明します。 +アクションハンドラはストアインスタンスのメソッドやプロパティのセットと同じものを呼び出せるコンテキストオブジェクトを受け取ります。したがって `context.commit` を呼び出すことでミューテーションをコミットできます。あるいは `context.state` や `context.getters` で、状態やゲッターにアクセスできます。他のアクションも `context.dispatch` で呼ぶこともできます。なぜコンテキストオブジェクトがストアインスタンスそのものではないのかは、後ほど[モジュール](modules.md)で説明します。 実際にはコードを少しシンプルにするために ES2015 の[引数分割束縛(argument destructuring)](https://github.com/lukehoban/es6features#destructuring)がよく使われます(特に `commit` を複数回呼び出す必要があるとき): diff --git a/docs/ja/guide/forms.md b/docs/ja/guide/forms.md index 075f3d9fa..2ad1f8922 100644 --- a/docs/ja/guide/forms.md +++ b/docs/ja/guide/forms.md @@ -1,5 +1,7 @@ # フォームの扱い + + 厳格モードで Vuex を使用するとき、Vuex に属する状態の一部で `v-model` を使用するのは少しトリッキーです: ``` html diff --git a/docs/ja/guide/getters.md b/docs/ja/guide/getters.md index c016b647a..417ce71de 100644 --- a/docs/ja/guide/getters.md +++ b/docs/ja/guide/getters.md @@ -1,5 +1,7 @@ # ゲッター + + 例えば項目のリストをフィルタリングしたりカウントするときのように、ストアの状態を算出したいときがあります。 ``` js diff --git a/docs/ja/guide/hot-reload.md b/docs/ja/guide/hot-reload.md index cfc7c5791..8024f77f7 100644 --- a/docs/ja/guide/hot-reload.md +++ b/docs/ja/guide/hot-reload.md @@ -28,9 +28,9 @@ if (module.hot) { module.hot.accept(['./mutations', './modules/a'], () => { // 更新されたモジュールをインポートする // babel 6 のモジュール出力のため、ここでは .default を追加しなければならない - const newActions = require('./actions').default const newMutations = require('./mutations').default - // 新しいアクションとミューテーションにスワップ + const newModuleA = require('./modules/a').default + // 新しいモジュールとミューテーションにスワップ store.hotUpdate({ mutations: newMutations, modules: { diff --git a/docs/ja/guide/modules.md b/docs/ja/guide/modules.md index e0c7794c5..c8fb33c25 100644 --- a/docs/ja/guide/modules.md +++ b/docs/ja/guide/modules.md @@ -1,5 +1,7 @@ # モジュール + + 単一ステートツリーを使うため、アプリケーションの全ての状態は、一つの大きなストアオブジェクトに内包されます。しかしながら、アプリケーションが大きくなるにつれて、ストアオブジェクトは膨れ上がってきます。 そのような場合に役立てるため Vuex ではストアを**モジュール**に分割できるようになっています。それぞれのモジュールは、モジュール自身の状態(state)、ミューテーション、アクション、ゲッター、モジュールさえも内包できます(モジュールをネストできます)- トップからボトムまでフラクタル構造です: diff --git a/docs/ja/guide/mutations.md b/docs/ja/guide/mutations.md index 77d6c864d..dfd4898e8 100644 --- a/docs/ja/guide/mutations.md +++ b/docs/ja/guide/mutations.md @@ -1,5 +1,7 @@ # ミューテーション + + 実際に Vuex のストアの状態を変更できる唯一の方法は、ミューテーションをコミットすることです。Vuex のミューテーションはイベントにとても近い概念です: 各ミューテーションは**タイプ**と**ハンドラ**を持ちます。ハンドラ関数は Vuex の状態(state)を第1引数として取得し、実際に状態の変更を行います: ``` js @@ -88,7 +90,7 @@ Vuex ストアの状態は Vue によってリアクティブになっている - `Vue.set(obj, 'newProp', 123)` を使用する。あるいは - - 全く新しいオブジェクトで既存のオブジェクトを置き換える。例えば、stage-3 の[スプレッドシンタックス(object spread syntax)](https://github.com/sebmarkbage/ecmascript-rest-spread) を使用して、次のように書くことができます: + - 全く新しいオブジェクトで既存のオブジェクトを置き換える。例えば、[スプレッドシンタックス(object spread syntax)](https://github.com/sebmarkbage/ecmascript-rest-spread) を使用して、次のように書くことができます: ``` js state.obj = { ...state.obj, newProp: 123 } diff --git a/docs/ja/guide/plugins.md b/docs/ja/guide/plugins.md index db4cf6f30..7420c7236 100644 --- a/docs/ja/guide/plugins.md +++ b/docs/ja/guide/plugins.md @@ -1,5 +1,7 @@ # プラグイン + + Vuex ストア は、各ミューテーションへのフックを公開する `plugins` オプションを受け付けます。 Vuex プラグインは、単一の引数としてストアを受けつけるただの関数です: ``` js @@ -25,7 +27,7 @@ const store = new Vuex.Store({ プラグインは直接、状態を変更できません。これはコンポーネントに似ています。プラグインはコンポーネント同様に、ミューテーションのコミットによる変更のトリガーだけで状態を変更できます。 -ミューテーションのコミットによるストアとデータソースの同期をプラグインで実現できます。 websocket データソースとストアを例にします (これは不自然な例です。実際には、さらに複雑なタスクのために `createPlugin` 関数は、追加でいくつかのオプションを受け取れます): +ミューテーションのコミットによるストアとデータソースの同期をプラグインで実現できます。 websocket データソースとストアを例にします (これは不自然な例です。実際には、さらに複雑なタスクのために `createWebSocketPlugin` 関数は、追加でいくつかのオプションを受け取れます): ``` js export default function createWebSocketPlugin (socket) { diff --git a/docs/ja/guide/state.md b/docs/ja/guide/state.md index c07a125b0..7141db0de 100644 --- a/docs/ja/guide/state.md +++ b/docs/ja/guide/state.md @@ -2,6 +2,8 @@ ### 単一ステートツリー + + Vuex は **単一ステートツリー (single state tree)** を使います。つまり、この単一なオブジェクトはアプリケーションレベルの状態が全て含まれており、"信頼できる唯一の情報源 (single source of truth)" として機能します。これは、通常、アプリケーションごとに1つしかストアは持たないことを意味します。単一ステートツリーは状態の特定の部分を見つけること、デバッグのために現在のアプリケーションの状態のスナップショットを撮ることを容易にします。 単一ステートツリーはモジュール性と競合しません。以降の章で、アプリケーションの状態とミューテーション(変更)をサブモジュールに分割する方法について説明します。 @@ -57,6 +59,8 @@ const Counter = { ### `mapState`  ヘルパー + + コンポーネントが複数のストアのステートプロパティやゲッターを必要としているとき、これらすべてにおいて、算出プロパティを宣言することは繰り返しで冗長です。これに対処するため、算出ゲッター関数を生成し、いくつかのキーストロークを省くのに役立つ `mapState` ヘルパーを使うことができます: ```js diff --git a/docs/ja/guide/testing.md b/docs/ja/guide/testing.md index 54a00d177..e3340ca20 100644 --- a/docs/ja/guide/testing.md +++ b/docs/ja/guide/testing.md @@ -1,5 +1,7 @@ # テスト + + 私たちが Vuex でユニットテストしたい主な部分はミューテーションとアクションです。 ### ミューテーションのテスト diff --git a/docs/kr/README.md b/docs/kr/README.md index 98730c268..87e2445fe 100644 --- a/docs/kr/README.md +++ b/docs/kr/README.md @@ -52,6 +52,8 @@ new Vue({ 이는 [Flux](https://facebook.github.io/flux/docs/overview.html), [Redux](http://redux.js.org/), [The Elm Architecture](https://guide.elm-lang.org/architecture/)에서 영감을 받은 Vuex의 기본 아이디어 입니다. 다른 패턴과 달리 Vuex는 Vue.js가 효율적인 업데이트를 위해 세분화된 반응 시스템을 활용하도록 특별히 고안된 라이브러리입니다. +대화식으로 Vuex를 배우고 싶다면 [Scrimba]((https://scrimba.com/g/gvuex))의 Vuex 과정에 등록하십시오. + ![vuex](/vuex.png) ### 언제 사용해야 하나요? diff --git a/docs/kr/guide/README.md b/docs/kr/guide/README.md index 4de68dc68..74cf732bc 100644 --- a/docs/kr/guide/README.md +++ b/docs/kr/guide/README.md @@ -1,5 +1,7 @@ # 시작하기 + + 모든 Vuex 애플리케이션의 중심에는 **store** 가 있습니다. "저장소"는 기본적으로 애플리케이션 **상태** 를 보유하고있는 컨테이너입니다. Vuex 저장소가 일반 전역 개체와 두 가지 다른 점이 있습니다. 1. Vuex store는 반응형 입니다. Vue 컴포넌트는 상태를 검색할 때 저장소의 상태가 변경되면 효율적으로 대응하고 업데이트합니다. diff --git a/docs/kr/guide/actions.md b/docs/kr/guide/actions.md index e36c24160..11dfcd215 100644 --- a/docs/kr/guide/actions.md +++ b/docs/kr/guide/actions.md @@ -1,5 +1,7 @@ # 액션 + + 액션은 변이와 유사합니다. 몇가지 다른 점은, - 상태를 변이시키는 대신 액션으로 변이에 대한 커밋을 합니다. diff --git a/docs/kr/guide/forms.md b/docs/kr/guide/forms.md index 03e7320c5..a332aa7ec 100644 --- a/docs/kr/guide/forms.md +++ b/docs/kr/guide/forms.md @@ -1,5 +1,7 @@ # 폼 핸들링 + + strict 모드로 Vuex를 사용하는 경우 Vuex에 포함된 부분에 `v-model`을 사용하는 것은 약간 까다로울 수 있습니다. ``` html diff --git a/docs/kr/guide/getters.md b/docs/kr/guide/getters.md index 0e42c5e1d..f38434f3e 100644 --- a/docs/kr/guide/getters.md +++ b/docs/kr/guide/getters.md @@ -1,5 +1,7 @@ # Getters + + 때로는 저장소 상태를 기반하는 상태를 계산해야 할 수도 있습니다.(예: 아이템 리스트를 필터링하고 계산) ``` js diff --git a/docs/kr/guide/modules.md b/docs/kr/guide/modules.md index 70a00267b..c2e1cd7ae 100644 --- a/docs/kr/guide/modules.md +++ b/docs/kr/guide/modules.md @@ -1,5 +1,7 @@ # 모듈 + + 단일 상태 트리를 사용하기 때문에 애플리케이션의 모든 상태가 하나의 큰 객체 안에 포함됩니다. 그러나 규모가 커짐에 따라 저장소는 매우 비대해질 수 있습니다. 이를 위해 Vuex는 저장소를 **모듈** 로 나눌 수 있습니다. 각 모듈은 자체 상태, 변이, 액션, 게터 및 심지어 중첩된 모듈을 포함 할 수 있습니다. diff --git a/docs/kr/guide/mutations.md b/docs/kr/guide/mutations.md index 6217890e4..1f7f0f156 100644 --- a/docs/kr/guide/mutations.md +++ b/docs/kr/guide/mutations.md @@ -1,5 +1,7 @@ # 변이 + + Vuex 저장소에서 실제로 상태를 변경하는 유일한 방법은 변이하는 것입니다. Vuex 변이는 이벤트와 매우 유사합니다. 각 변이에는 **타입** 문자열 **핸들러** 가 있습니다. 핸들러 함수는 실제 상태 수정을 하는 곳이며, 첫 번째 전달인자로 상태를받습니다. ``` js diff --git a/docs/kr/guide/plugins.md b/docs/kr/guide/plugins.md index 39c264d23..a90cd64a4 100644 --- a/docs/kr/guide/plugins.md +++ b/docs/kr/guide/plugins.md @@ -1,5 +1,7 @@ # 플러그인 + + Vuex 저장소는 각 변이에 대한 훅을 노출하는 `plugins` 옵션을 허용합니다. Vuex 플러그인은 저장소를 유일한 전달인자로 받는 함수입니다. ``` js diff --git a/docs/kr/guide/state.md b/docs/kr/guide/state.md index 4827b6bb0..5dc74f04e 100644 --- a/docs/kr/guide/state.md +++ b/docs/kr/guide/state.md @@ -2,6 +2,8 @@ ### 단일 상태 트리 + + Vuex는 **단일 상태 트리** 를 사용합니다. 즉, 이 단일 객체는 모든 애플리케이션 수준의 상태를 포함하며 "원본 소스" 역할을 합니다. 이는 각 애플리케이션마다 하나의 저장소만 갖게 된다는 것을 의미합니다. 단일 상태 트리를 사용하면 특정 상태를 쉽게 찾을 수 있으므로 디버깅을 위해 현재 앱 상태의 스냅 샷을 쉽게 가져올 수 있습니다. 단일 상태 트리는 모듈성과 충돌하지 않습니다. 나중에 상태와 변이를 하위 모듈로 분할하는 방법에 대해 설명합니다. @@ -58,6 +60,8 @@ const Counter = { ### `mapState` 헬퍼 + + 컴포넌트가 여러 저장소 상태 속성이나 getter를 사용해야하는 경우 계산된 속성을 모두 선언하면 반복적이고 장황해집니다. 이를 처리하기 위해 우리는 계산된 getter 함수를 생성하는 `mapState` 헬퍼를 사용하여 키 입력을 줄일 수 있습니다. ``` js diff --git a/docs/kr/guide/testing.md b/docs/kr/guide/testing.md index 27cc99fd6..64d9f5694 100644 --- a/docs/kr/guide/testing.md +++ b/docs/kr/guide/testing.md @@ -1,5 +1,7 @@ # 테스팅 + + Vuex에서 단위 테스트를 하고자 하는 주요 부분은 변이와 액션입니다. ### 변이 테스팅 diff --git a/docs/ptbr/README.md b/docs/ptbr/README.md index 39e352450..f80f96de0 100644 --- a/docs/ptbr/README.md +++ b/docs/ptbr/README.md @@ -1,5 +1,7 @@ # O que é Vuex? + + O Vuex é um **padrão de gerenciamento de estado + biblioteca** para aplicativos Vue.js. Ele serve como um _store_ centralizado para todos os componentes em uma aplicação, com regras garantindo que o estado só possa ser mutado de forma previsível. Ele também se integra com a extensão oficial [Vue devtools](https://github.com/vuejs/vue-devtools) para fornecer recursos avançados sem configurações adicionais, como depuração viajando pelo histórico de estado (_time travel_) e exportação/importação de registros de estado em determinado momento. ### O que é um "Padrão de Gerenciamento do Estado"? @@ -52,12 +54,14 @@ Além disso, definindo e separando os conceitos envolvidos no gerenciamento do e Esta é a ideia básica por trás do Vuex, inspirada por [Flux](https://facebook.github.io/flux/docs/overview.html), [Redux](http://redux.js.org/) e [The Elm Architecture](https://guide.elm-lang.org/architecture/). Ao contrário dos outros padrões, o Vuex também é uma implementação de biblioteca adaptada especificamente para o Vue.js tirar proveito de seu sistema de reatividade granular para atualizações eficientes. +Se você quiser aprender Vuex de um modo interativo, você pode conferir esse curso de [Vuex no Scrimba.](https://scrimba.com/g/gvuex) + ![vuex](/vuex.png) ### Quando usar o Vuex? Embora o Vuex nos ajude a lidar com o gerenciamento de estado compartilhado, ele também vem com o custo de mais conceitos e códigos repetitivos. É uma escolha de prós e contras entre produtividade de curto e longo prazo -Se você nunca construiu um SPA em grande escala e for direto para o Vuex, ele pode parecer detalhado e desanimador. Isso é perfeitamente normal - se o seu aplicativo é simples, você provavelmente ficará bem sem o Vuex. Um simples [global event bus](https://br.vuejs.org/v2/guide/components.html#Comunicacao-Nao-Pai-Filho) pode ser tudo que você precisa. Mas, se você está criando um SPA de médio a grande porte, é provável que tenha encontrado situações que fazem você pensar em como lidar melhor com o estado fora de seus componentes do Vue, e o Vuex será o próximo passo natural para você. Há uma boa citação de Dan Abramov, o autor do Redux: +Se você nunca construiu um SPA em grande escala e for direto para o Vuex, ele pode parecer detalhado e desanimador. Isso é perfeitamente normal - se o seu aplicativo é simples, você provavelmente ficará bem sem o Vuex. Um simples [store pattern](https://br.vuejs.org/v2/guide/state-management.html#Gerenciamento-de-Estado-do-Zero) pode ser tudo que você precisa. Mas, se você está criando um SPA de médio a grande porte, é provável que tenha encontrado situações que fazem você pensar em como lidar melhor com o estado fora de seus componentes do Vue, e o Vuex será o próximo passo natural para você. Há uma boa citação de Dan Abramov, o autor do Redux: > As bibliotecas _Flux_ são como óculos: você saberá quando precisar delas. diff --git a/docs/ptbr/api/README.md b/docs/ptbr/api/README.md index 4b48309a9..5c8009909 100644 --- a/docs/ptbr/api/README.md +++ b/docs/ptbr/api/README.md @@ -116,6 +116,19 @@ const store = new Vuex.Store({ ...options }) [Detalhes](../guide/strict.md) +### devtools + +- type: `Boolean` + + Ative ou desative as ferramentas de desenvolvedor para uma determinada instância vuex. Passar _false_ à instância diz ao _store_ Vuex para não se integrar ao _devtools_. Útil para quando se tem vários _stores_ em uma _single page_. + + ``` js + { + devtools: false + } + ``` + + ## Vuex.Store Propriedades da Instância ### state @@ -194,6 +207,21 @@ const store = new Vuex.Store({ ...options }) Para cancelar a assinatura, chame a função _unsubscribe_ retornada. + > Novo em 3.1.0 + + A partir da 3.1.0, `subscribeAction` também pode especificar se o manipulador do _subscribe_ deve ser chamado *antes de* ou *depois de* um despacho de ação (o comportamento padrão é *antes*): + + ``` js + store.subscribeAction({ + before: (action, state) => { + console.log(`antes da action ${action.type}`) + }, + after: (action, state) => { + console.log(`depois da action ${action.type}`) + } + }) + ``` + Mais comumente usado em plugins. [Detalhes](../guide/plugins.md) ### registerModule diff --git a/docs/ptbr/guide/README.md b/docs/ptbr/guide/README.md index f9f56360a..388d93f02 100644 --- a/docs/ptbr/guide/README.md +++ b/docs/ptbr/guide/README.md @@ -1,5 +1,7 @@ # Começando + + No centro de cada aplicação Vuex existe o **_store_**. Um "_store_" é basicamente um recipiente que contém o **estado** da sua aplicação. Há duas coisas que tornam um _store_ Vuex diferente de um objeto global simples: 1. Os _stores_ Vuex são reativos. Quando os componentes do Vue obtêm o estado dele, eles atualizarão de forma reativa e eficiente se o estado do _store_ mudar. diff --git a/docs/ptbr/guide/actions.md b/docs/ptbr/guide/actions.md index 8a66eab85..506d4d84c 100644 --- a/docs/ptbr/guide/actions.md +++ b/docs/ptbr/guide/actions.md @@ -1,5 +1,7 @@ # Ações + + As ações são semelhantes às mutações, as diferenças são as seguintes: - Em vez de mudar o estado, as ações confirmam (ou fazem _commit_ de) mutações. diff --git a/docs/ptbr/guide/forms.md b/docs/ptbr/guide/forms.md index 0b1564324..14cd0d724 100644 --- a/docs/ptbr/guide/forms.md +++ b/docs/ptbr/guide/forms.md @@ -1,5 +1,7 @@ # Manipulação de Formulários + + Ao usar o Vuex no modo estrito, pode ser um pouco complicado usar `v-model` em um pedaço do estado que pertence ao Vuex: ``` html diff --git a/docs/ptbr/guide/getters.md b/docs/ptbr/guide/getters.md index b74b7f226..bc0922f4d 100644 --- a/docs/ptbr/guide/getters.md +++ b/docs/ptbr/guide/getters.md @@ -1,5 +1,7 @@ # Getters + + Às vezes, talvez precisemos calcular o estado derivado com base no estado do _store_, por exemplo, filtrar através de uma lista de itens e contá-los: ``` js diff --git a/docs/ptbr/guide/modules.md b/docs/ptbr/guide/modules.md index 2e05e3308..306c50c7f 100644 --- a/docs/ptbr/guide/modules.md +++ b/docs/ptbr/guide/modules.md @@ -1,5 +1,7 @@ # Módulos + + Devido ao uso de uma única árvore de estado, todo o estado de nossa aplicação está contido dentro de um grande objeto. No entanto, à medida que nosso aplicativo cresce em escala, o _store_ pode ficar realmente inchado. Para ajudar com isso, o Vuex nos permite dividir nosso _store_ em **módulos**. Cada módulo pode conter seu próprio estado, mutações, ações, _getters_ e até módulos aninhados - é todo o complexo caminho abaixo: @@ -296,8 +298,12 @@ O registro de módulo dinâmico possibilita que outros plug-ins Vue aproveitem t Você também pode remover um módulo dinamicamente registrado com o `store.unregisterModule(moduleName)`. Note que você não pode remover módulos estáticos (declarados na criação do _store_) com este método. +#### Preservando o estado + É bem provável que você queira preservar o estado anterior ao registrar um novo módulo, como preservar o estado de um aplicativo Renderizado no Lado do Servidor (_Server_ _Side_ _Rendered_). Você pode fazer isso com a opção `preserveState`:`store.registerModule('a', module, {preserveState: true})` +Quando você informa `preserveState: true`, o módulo é registrado, as ações, mutações e _getters_ são incluídos no _store_, mas o estado não. É assumido que estado da sua _store_ já contém um estado para aquele módulo e você não quer sobrescrevê-lo. + ### Reutilização do Módulo Às vezes, podemos precisar criar várias instâncias de um módulo, por exemplo: @@ -307,9 +313,7 @@ Você também pode remover um módulo dinamicamente registrado com o `store.unre Se usarmos um objeto simples para declarar o estado do módulo, esse objeto de estado será compartilhado por referência e causará poluição entre estados de _store_/módulo quando ele sofrer uma mutação. -This is actually the exact same problem with `data` inside Vue components. So the solution is also the same - use a function for declaring module state (supported in 2.3.0+): - -Este é exatamente o mesmo problema com _data_ dentro dos componentes do Vue. Então a solução também é a mesma - use uma função para declarar o estado do módulo (suportado em 2.3.0+): +Este é exatamente o mesmo problema com `data` dentro dos componentes Vue. Então, a solução também é a mesma - use uma função para declarar o estado do módulo (suportado em 2.3.0+): ``` js const MyReusableModule = { diff --git a/docs/ptbr/guide/mutations.md b/docs/ptbr/guide/mutations.md index f2a5a5805..773f659ec 100644 --- a/docs/ptbr/guide/mutations.md +++ b/docs/ptbr/guide/mutations.md @@ -1,5 +1,7 @@ # Mutações + + A única maneira de realmente mudar de estado em um _store_ Vuex é por confirmar (ou fazer _commit_ de) uma mutação. As mutações do Vuex são muito semelhantes aos eventos: cada mutação tem uma cadeia de caracteres **tipo** e um **manipulador**. A função do manipulador é onde realizamos modificações de estado reais e ele receberá o estado como o 1º argumento: ``` js diff --git a/docs/ptbr/guide/plugins.md b/docs/ptbr/guide/plugins.md index 2e670a3ca..cc0f0fae4 100644 --- a/docs/ptbr/guide/plugins.md +++ b/docs/ptbr/guide/plugins.md @@ -1,5 +1,7 @@ # Plugins + + Os _stores_ do Vuex aceitam a opção _plugins_ que expõe _hooks_ para cada mutação. Um _plugin_ Vuex é simplesmente uma função que recebe um _store_ como seu único argumento: ``` js diff --git a/docs/ptbr/guide/state.md b/docs/ptbr/guide/state.md index 60a67750d..2f4ec24b3 100644 --- a/docs/ptbr/guide/state.md +++ b/docs/ptbr/guide/state.md @@ -2,6 +2,8 @@ ### Árvore Única de Estado + + O Vuex usa uma **árvore única de estado** - ou seja, esse único objeto contém todo o estado do seu nível de aplicação e serve como a "única fonte da verdade". Isso também significa que você terá apenas um _store_ para cada aplicativo. Uma árvore única de estado facilita a localização de uma parte específica do estado, e permite capturar facilmente momentos do estado atual do aplicativo para fins de depuração. A árvore única de estado não entra em conflito com a modularidade - em capítulos posteriores, discutiremos como dividir seu estado e mutações em sub-módulos. @@ -58,6 +60,8 @@ const Counter = { ### O Auxiliar `mapState` + + Quando um componente precisa fazer uso de várias propriedades do estado do _store_ ou _getters_, declarar todos esses dados computados pode ser repetitivo e verboso. Para lidar com isso, podemos usar o auxiliar `mapState` que gera funções _getter_ computadas para nós, economizando algumas linhas de código: ``` js diff --git a/docs/ptbr/guide/testing.md b/docs/ptbr/guide/testing.md index 38101e43e..ecffbcb0d 100644 --- a/docs/ptbr/guide/testing.md +++ b/docs/ptbr/guide/testing.md @@ -1,5 +1,7 @@ # Testando + + As partes principais que queremos testar no Vuex são mutações e ações. ### Testando Mutações diff --git a/docs/ru/README.md b/docs/ru/README.md index 4b6656521..d8523f05b 100644 --- a/docs/ru/README.md +++ b/docs/ru/README.md @@ -1,5 +1,7 @@ # Что такое Vuex? + + Vuex — **паттерн управления состоянием + библиотека** для приложений на Vue.js. Он служит централизованным хранилищем данных для всех компонентов приложения с правилами, гарантирующими, что состояние может быть изменено только предсказуемым образом. Vuex интегрируется с официальным расширением [vue-devtools](https://github.com/vuejs/vue-devtools), предоставляя «из коробки» такие продвинутые возможности, как «машину времени» для отладки и экспорт/импорт слепков состояния данных. ### Что такое «паттерн управления состоянием»? @@ -52,6 +54,8 @@ new Vue({ Это основная идея Vuex, вдохновлённого [Flux](https://facebook.github.io/flux/docs/overview.html), [Redux](http://redux.js.org/) и [Архитектурой Elm](https://guide.elm-lang.org/architecture/). В отличие от других паттернов, Vuex реализован в виде библиотеки, специально предназначенной для Vue.js, чтобы использовать его систему реактивности для эффективного обновления. +Если хотите изучить Vuex в интерактивном режиме, попробуйте этот [курс по Vuex на Scrimba.](https://scrimba.com/g/gvuex) + ![vuex](/ru/vuex.png) ### Когда следует использовать Vuex? diff --git a/docs/ru/api/README.md b/docs/ru/api/README.md index ac5f117dc..93256d883 100644 --- a/docs/ru/api/README.md +++ b/docs/ru/api/README.md @@ -120,7 +120,7 @@ const store = new Vuex.Store({ ...options }); * тип: `Boolean` - Интеграция в devtools конкретного экземпляра Vuex. Например, передача `false` сообщает экземпляру хранилища Vuex, что не требуется подписываться на плагин devtools. Это будет полезно если у вас несколько хранилищ на одной странице. + Интеграция в devtools конкретного экземпляра Vuex. Например, передача `false` сообщает экземпляру хранилища Vuex, что не требуется подписываться на плагин devtools. Это будет полезно если у вас несколько хранилищ на одной странице. ``` js { @@ -206,6 +206,21 @@ store.subscribeAction((action, state) => { Для прекращения отслеживания, необходимо вызвать возвращаемую методом функцию. +> Добавлено в версии 3.1.0 + +Начиная с версии 3.1.0, в `subscribeAction` также можно определять, должен ли обработчик вызываться *до* или *после* вызова действия (по умолчанию поведение *до*): + +``` js +store.subscribeAction({ + before: (action, state) => { + console.log(`before action ${action.type}`) + }, + after: (action, state) => { + console.log(`after action ${action.type}`) + } +}) +``` + Чаще всего используется в плагинах. [Подробнее](../guide/plugins.md) ### registerModule diff --git a/docs/ru/guide/README.md b/docs/ru/guide/README.md index 68a98df75..a20c0acad 100644 --- a/docs/ru/guide/README.md +++ b/docs/ru/guide/README.md @@ -1,5 +1,7 @@ # Введение + + В центре любого Vuex-приложения находится **хранилище**. «Хранилище» — это контейнер, в котором хранится **состояние** вашего приложения. Два момента отличают хранилище Vuex от простого глобального объекта: 1. Хранилище Vuex реактивно. Когда компоненты Vue полагаются на его состояние, то они будут реактивно и эффективно обновляться, если состояние хранилища изменяется. diff --git a/docs/ru/guide/actions.md b/docs/ru/guide/actions.md index 42d0c4460..511c5ca45 100644 --- a/docs/ru/guide/actions.md +++ b/docs/ru/guide/actions.md @@ -1,5 +1,7 @@ # Действия + + Действия — похожи на мутации с несколькими отличиями: * Вместо того, чтобы напрямую менять состояние, действия инициируют мутации; diff --git a/docs/ru/guide/forms.md b/docs/ru/guide/forms.md index 0658f2b96..239c16ef2 100644 --- a/docs/ru/guide/forms.md +++ b/docs/ru/guide/forms.md @@ -1,5 +1,7 @@ # Работа с формами + + При использовании строгого режима Vuex может показаться неочевидным как использовать `v-model` с частью состояния Vuex: ```html diff --git a/docs/ru/guide/getters.md b/docs/ru/guide/getters.md index 9c9d1daed..e0f126ef0 100644 --- a/docs/ru/guide/getters.md +++ b/docs/ru/guide/getters.md @@ -1,5 +1,7 @@ # Геттеры + + Иногда может потребоваться вычислять производное состояние на основе состояния хранилища, например, отфильтровать список и затем подсчитать количество элементов: ```js diff --git a/docs/ru/guide/modules.md b/docs/ru/guide/modules.md index b4f53cd10..70187bcc3 100644 --- a/docs/ru/guide/modules.md +++ b/docs/ru/guide/modules.md @@ -1,5 +1,7 @@ # Модули + + Из-за использования единого дерева состояния, все глобальные данные приложения оказываются помещены в один большой объект. По мере роста приложения, хранилище может существенно раздуться. Чтобы помочь в этой беде, Vuex позволяет разделять хранилище на **модули**. Каждый модуль может содержать собственное состояние, мутации, действия, геттеры и даже встроенные подмодули — структура фрактальна: @@ -292,7 +294,11 @@ store.registerModule(['nested', 'myModule'], { Удалить динамически зарегистрированный модуль можно с помощью `store.unregisterModule(moduleName)`. Обратите внимание, что статические (определённые на момент создания хранилища) модули при помощи этого метода удалить не получится. -Вероятно, вы хотите сохранить предыдущее состояние при регистрации нового модуля, например сохранить состояние из приложения с рендерингом на стороне сервера. Вы можете этого добиться с помощью опции `preserveState`: `store.registerModule('a', module, { preserveState: true })` +#### Сохранение состояния + +Вероятно, вы хотите сохранить предыдущее состояние при регистрации нового модуля, например сохранить состояние из приложения с рендерингом на стороне сервера. Вы можете этого добиться с помощью опции `preserveState`: `store.registerModule('a', module, { preserveState: true })`. + +При использовании `preserveState: true` модуль регистрируется, действия, мутации и геттеры добавляются в хранилище, а состояние нет. Предполагается, что состояние вашего хранилища уже содержит состояние для этого модуля и нет необходимости его перезаписывать. ### Повторное использование модулей diff --git a/docs/ru/guide/mutations.md b/docs/ru/guide/mutations.md index 7b1530a15..81033830a 100644 --- a/docs/ru/guide/mutations.md +++ b/docs/ru/guide/mutations.md @@ -1,5 +1,7 @@ # Мутации + + Единственным способом изменения состояния хранилища во Vuex являются мутации. Мутации во Vuex очень похожи на события: каждая мутация имеет строковый **тип** и **функцию-обработчик**. В этом обработчике и происходят, собственно, изменения состояния, переданного в функцию первым аргументом: ```js diff --git a/docs/ru/guide/plugins.md b/docs/ru/guide/plugins.md index 03f1cce64..b52b18c81 100644 --- a/docs/ru/guide/plugins.md +++ b/docs/ru/guide/plugins.md @@ -1,5 +1,7 @@ # Плагины + + Хранилища Vuex принимают опцию `plugins`, предоставляющую хуки для каждой мутации. Vuex-плагин — это просто функция, получающая хранилище в качестве единственного параметра: ```js diff --git a/docs/ru/guide/state.md b/docs/ru/guide/state.md index 274a7995d..462d66457 100644 --- a/docs/ru/guide/state.md +++ b/docs/ru/guide/state.md @@ -2,6 +2,8 @@ ### Единое дерево состояния + + Vuex использует **единое дерево состояния** — когда один объект содержит всё глобальное состояние приложения и служит «единственным источником истины». Это также означает, что в приложении будет только одно такое хранилище. Единое дерево состояния позволяет легко найти нужную его часть или делать снимки текущего состояния приложения в целях отладки. Единое дерево состояния не противоречит модульности — в следующих главах мы изучим, как можно разделить состояние и мутации на под-модули. @@ -58,6 +60,8 @@ const Counter = { ### Вспомогательная функция `mapState` + + Когда компонент должен использовать множество свойств или геттеров хранилища, объявлять все эти вычисляемые свойства может быть утомительно. В таких случаях можно использовать функцию `mapState`, которая автоматически генерирует вычисляемые свойства: ```js diff --git a/docs/ru/guide/testing.md b/docs/ru/guide/testing.md index 616c16ac2..bd77ea655 100644 --- a/docs/ru/guide/testing.md +++ b/docs/ru/guide/testing.md @@ -1,5 +1,7 @@ # Тестирование + + В основном предметом модульного тестирования во Vuex являются мутации и действия. ### Тестирование мутаций diff --git a/docs/zh/README.md b/docs/zh/README.md index bc0e73ba9..a4e55325d 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -33,7 +33,7 @@ new Vue({ - **view**,以声明方式将 **state** 映射到视图; - **actions**,响应在 **view** 上的用户输入导致的状态变化。 -以下是一个表示“单向数据流”理念的极简示意: +以下是一个表示“单向数据流”理念的简单示意:

@@ -48,15 +48,17 @@ new Vue({ 因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为! -另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。 +通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。 -这就是 Vuex 背后的基本思想,借鉴了 [Flux](https://facebook.github.io/flux/docs/overview.html)、[Redux](http://redux.js.org/)、和 [The Elm Architecture](https://guide.elm-lang.org/architecture/)。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。 +这就是 Vuex 背后的基本思想,借鉴了 [Flux](https://facebook.github.io/flux/docs/overview.html)、[Redux](http://redux.js.org/) 和 [The Elm Architecture](https://guide.elm-lang.org/architecture/)。与其他模式不同的是,Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。 + +如果你想交互式地学习 Vuex,可以看这个 [Scrimba 上的 Vuex 课程](https://scrimba.com/g/gvuex),它将录屏和代码试验场混合在了一起,你可以随时暂停并尝试。 ![vuex](/vuex.png) ### 什么情况下我应该使用 Vuex? -虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 +Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。 如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 [store 模式](https://cn.vuejs.org/v2/guide/state-management.html#简单状态管理起步使用)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是: diff --git a/docs/zh/api/README.md b/docs/zh/api/README.md index e1296acc2..3378a320b 100644 --- a/docs/zh/api/README.md +++ b/docs/zh/api/README.md @@ -170,7 +170,7 @@ const store = new Vuex.Store({ ...options }) - `watch(fn: Function, callback: Function, options?: Object): Function` -  响应式地侦听 `fn` 的返回值,当值改变时调用回调函数。`fn` 接收 store 的 state 作为第一个参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 [`vm.$watch`](https://cn.vuejs.org/v2/api/#watch) 方法的参数。 +  响应式地侦听 `fn` 的返回值,当值改变时调用回调函数。`fn` 接收 store 的 state 作为第一个参数,其 getter 作为第二个参数。最后接收一个可选的对象参数表示 Vue 的 [`vm.$watch`](https://cn.vuejs.org/v2/api/#vm-watch) 方法的参数。  要停止侦听,调用此方法返回的函数即可停止侦听。 @@ -208,6 +208,21 @@ const store = new Vuex.Store({ ...options }) 要停止订阅,调用此方法返回的函数即可停止订阅。 + > 3.1.0 新增 + + 从 3.1.0 起,`subscribeAction` 也可以指定订阅处理函数的被调用时机应该在一个 action 分发*之前*还是*之后* (默认行为是*之前*): + + ``` js + store.subscribeAction({ + before: (action, state) => { + console.log(`before action ${action.type}`) + }, + after: (action, state) => { + console.log(`after action ${action.type}`) + } + }) + ``` + 该功能常用于插件。[详细介绍](../guide/plugins.md) ### registerModule diff --git a/docs/zh/guide/README.md b/docs/zh/guide/README.md index eab92199c..dfe4b7ae9 100644 --- a/docs/zh/guide/README.md +++ b/docs/zh/guide/README.md @@ -1,5 +1,7 @@ # 开始 +

+ 每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的**状态 (state)**。Vuex 和单纯的全局对象有以下两点不同: 1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。 diff --git a/docs/zh/guide/actions.md b/docs/zh/guide/actions.md index d4ea1d8f2..a829af4d0 100644 --- a/docs/zh/guide/actions.md +++ b/docs/zh/guide/actions.md @@ -1,5 +1,7 @@ # Action + + Action 类似于 mutation,不同在于: - Action 提交的是 mutation,而不是直接变更状态。 diff --git a/docs/zh/guide/forms.md b/docs/zh/guide/forms.md index 99e7f041c..2d2dca19e 100644 --- a/docs/zh/guide/forms.md +++ b/docs/zh/guide/forms.md @@ -1,5 +1,7 @@ # 表单处理 + + 当在严格模式中使用 Vuex 时,在属于 Vuex 的 state 上使用 `v-model` 会比较棘手: ``` html diff --git a/docs/zh/guide/getters.md b/docs/zh/guide/getters.md index b8e2f729f..6cfe163d8 100644 --- a/docs/zh/guide/getters.md +++ b/docs/zh/guide/getters.md @@ -1,5 +1,7 @@ # Getter + + 有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数: ``` js diff --git a/docs/zh/guide/modules.md b/docs/zh/guide/modules.md index c210d3c66..72950239f 100644 --- a/docs/zh/guide/modules.md +++ b/docs/zh/guide/modules.md @@ -1,5 +1,7 @@ # Module + + 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成**模块(module)**。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割: @@ -294,8 +296,12 @@ store.registerModule(['nested', 'myModule'], { 你也可以使用 `store.unregisterModule(moduleName)` 来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)。 +#### 保留 state + 在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 `preserveState` 选项将其归档:`store.registerModule('a', module, { preserveState: true })`。 +当你设置 `preserveState: true` 时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。 + ### 模块重用 有时我们可能需要创建一个模块的多个实例,例如: diff --git a/docs/zh/guide/mutations.md b/docs/zh/guide/mutations.md index 76e8be330..25dccb5ac 100644 --- a/docs/zh/guide/mutations.md +++ b/docs/zh/guide/mutations.md @@ -1,5 +1,7 @@ # Mutation + + 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 **事件类型 (type)** 和 一个 **回调函数 (handler)**。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数: ``` js diff --git a/docs/zh/guide/plugins.md b/docs/zh/guide/plugins.md index 92c157c9c..a68134ec6 100644 --- a/docs/zh/guide/plugins.md +++ b/docs/zh/guide/plugins.md @@ -1,5 +1,7 @@ # 插件 + + Vuex 的 store 接受 `plugins` 选项,这个选项暴露出每次 mutation 的钩子。Vuex 插件就是一个函数,它接收 store 作为唯一参数: ``` js diff --git a/docs/zh/guide/state.md b/docs/zh/guide/state.md index 892773abb..1d543ae54 100644 --- a/docs/zh/guide/state.md +++ b/docs/zh/guide/state.md @@ -2,6 +2,8 @@ ### 单一状态树 + + Vuex 使用**单一状态树**——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 ([SSOT](https://en.wikipedia.org/wiki/Single_source_of_truth))”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 单状态树和模块化并不冲突——在后面的章节里我们会讨论如何将状态和状态变更事件分布到各个子模块中。 @@ -57,6 +59,8 @@ const Counter = { ### `mapState` 辅助函数 + + 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 `mapState` 辅助函数帮助我们生成计算属性,让你少按几次键: ``` js @@ -91,7 +95,7 @@ computed: mapState([ ### 对象展开运算符 -`mapState` 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 `computed` 属性。但是自从有了[对象展开运算符](https://github.com/sebmarkbage/ecmascript-rest-spread)(现处于 ECMASCript 提案 stage-4 阶段),我们可以极大地简化写法: +`mapState` 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 `computed` 属性。但是自从有了[对象展开运算符](https://github.com/sebmarkbage/ecmascript-rest-spread)(现处于 ECMAScript 提案 stage-4 阶段),我们可以极大地简化写法: ``` js computed: { diff --git a/docs/zh/guide/strict.md b/docs/zh/guide/strict.md index ad71ce59f..fc6175b8e 100644 --- a/docs/zh/guide/strict.md +++ b/docs/zh/guide/strict.md @@ -13,7 +13,7 @@ const store = new Vuex.Store({ ### 开发环境与发布环境 -**不要在发布环境下启用严格模式!**严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。 +**不要在发布环境下启用严格模式**!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。 类似于插件,我们可以让构建工具来处理这种情况: diff --git a/docs/zh/guide/testing.md b/docs/zh/guide/testing.md index b2cbc4579..ce511abba 100644 --- a/docs/zh/guide/testing.md +++ b/docs/zh/guide/testing.md @@ -1,5 +1,7 @@ # 测试 + + 我们主要想针对 Vuex 中的 mutation 和 action 进行单元测试。 ### 测试 Mutation diff --git a/package.json b/package.json index 6ed745e46..23f421901 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "vuex", - "version": "3.1.0", + "version": "3.1.1", "description": "state management for Vue.js", "main": "dist/vuex.common.js", "module": "dist/vuex.esm.js", "unpkg": "dist/vuex.js", + "jsdelivr": "dist/vuex.js", "typings": "types/index.d.ts", "files": [ "dist", @@ -60,9 +61,9 @@ "rollup-plugin-buble": "^0.19.6", "rollup-plugin-replace": "^2.1.0", "selenium-server": "^2.53.1", + "terser": "^3.17.0", "todomvc-app-css": "^2.1.0", "typescript": "^3.2.2", - "uglify-js": "^3.1.2", "vue": "^2.5.22", "vue-loader": "^15.2.1", "vue-template-compiler": "^2.5.22", diff --git a/src/plugins/devtool.js b/src/plugins/devtool.js index a6df685f0..dc6b04f73 100644 --- a/src/plugins/devtool.js +++ b/src/plugins/devtool.js @@ -1,6 +1,9 @@ -const devtoolHook = - typeof window !== 'undefined' && - window.__VUE_DEVTOOLS_GLOBAL_HOOK__ +const target = typeof window !== 'undefined' + ? window + : typeof global !== 'undefined' + ? global + : {} +const devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__ export default function devtoolPlugin (store) { if (!devtoolHook) return diff --git a/src/store.js b/src/store.js index dfcc755f8..a79252b50 100644 --- a/src/store.js +++ b/src/store.js @@ -1,7 +1,7 @@ import applyMixin from './mixin' import devtoolPlugin from './plugins/devtool' import ModuleCollection from './module/module-collection' -import { forEachValue, isObject, isPromise, assert } from './util' +import { forEachValue, isObject, isPromise, assert, partial } from './util' let Vue // bind on install @@ -256,7 +256,9 @@ function resetStoreVM (store, state, hot) { const computed = {} forEachValue(wrappedGetters, (fn, key) => { // use computed to leverage its lazy-caching mechanism - computed[key] = () => fn(store) + // direct inline function use will lead to closure preserving oldVm. + // using partial to return function with only arguments preserved in closure enviroment. + computed[key] = partial(fn, store) Object.defineProperty(store.getters, key, { get: () => store._vm[key], enumerable: true // for local getters diff --git a/src/util.js b/src/util.js index fb1203251..2df74bb16 100644 --- a/src/util.js +++ b/src/util.js @@ -64,3 +64,9 @@ export function isPromise (val) { export function assert (condition, msg) { if (!condition) throw new Error(`[vuex] ${msg}`) } + +export function partial (fn, arg) { + return function () { + return fn(arg) + } +} diff --git a/types/index.d.ts b/types/index.d.ts index 46316bb2a..3cd40067a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -97,7 +97,7 @@ export interface StoreOptions { strict?: boolean; } -export type ActionHandler = (this: Store, injectee: ActionContext, payload: any) => any; +export type ActionHandler = (this: Store, injectee: ActionContext, payload?: any) => any; export interface ActionObject { root?: boolean; handler: ActionHandler; @@ -105,7 +105,7 @@ export interface ActionObject { export type Getter = (state: S, getters: any, rootState: R, rootGetters: any) => any; export type Action = ActionHandler | ActionObject; -export type Mutation = (state: S, payload: any) => any; +export type Mutation = (state: S, payload?: any) => any; export type Plugin = (store: Store) => any; export interface Module { diff --git a/yarn.lock b/yarn.lock index 4602ea812..0871218f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2729,6 +2729,11 @@ commander@^2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== +commander@^2.19.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== + common-tags@^1.4.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -8406,6 +8411,14 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" +source-map-support@~0.5.10: + version "0.5.12" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" + integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@~0.5.6: version "0.5.10" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" @@ -8825,6 +8838,15 @@ terser-webpack-plugin@^1.1.0: webpack-sources "^1.1.0" worker-farm "^1.5.2" +terser@^3.17.0: + version "3.17.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" + integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + terser@^3.8.1: version "3.14.1" resolved "https://registry.yarnpkg.com/terser/-/terser-3.14.1.tgz#cc4764014af570bc79c79742358bd46926018a32" @@ -9057,7 +9079,7 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg== -uglify-js@3.4.x, uglify-js@^3.1.2: +uglify-js@3.4.x: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy