diff --git a/src/helpers.js b/src/helpers.js index 5922eb8b5..b4715f73d 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -11,7 +11,13 @@ export const mapState = normalizeNamespace((namespace, states) => { let state = this.$store.state let getters = this.$store.getters if (namespace) { - const module = getModuleByNamespace(this.$store, 'mapState', namespace) + if (typeof namespace === 'function') { + namespace = namespace.call(this, this) + if (namespace.charAt(namespace.length - 1) !== '/') { + namespace += '/' + } + } + const module = getModuleByNamespace(this, 'mapState', namespace) if (!module) { return } @@ -41,7 +47,7 @@ export const mapMutations = normalizeNamespace((namespace, mutations) => { // Get the commit method from store let commit = this.$store.commit if (namespace) { - const module = getModuleByNamespace(this.$store, 'mapMutations', namespace) + const module = getModuleByNamespace(this, 'mapMutations', namespace) if (!module) { return } @@ -64,17 +70,21 @@ export const mapMutations = normalizeNamespace((namespace, mutations) => { export const mapGetters = normalizeNamespace((namespace, getters) => { const res = {} normalizeMap(getters).forEach(({ key, val }) => { - // The namespace has been mutated by normalizeNamespace - val = namespace + val + let nsVal res[key] = function mappedGetter () { - if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) { + if (namespace && !getModuleByNamespace(this, 'mapGetters', namespace)) { return } - if (process.env.NODE_ENV !== 'production' && !(val in this.$store.getters)) { - console.error(`[vuex] unknown getter: ${val}`) + if (!nsVal) { + // The namespace has been mutated by normalizeNamespace/getModuleByNamespace + // Only perform this once to avoid re-namespacing on subsequent accesses + nsVal = getInstanceSpecificNamespace(this, namespace) + val + } + if (process.env.NODE_ENV !== 'production' && !(nsVal in this.$store.getters)) { + console.error(`[vuex] unknown getter: ${nsVal}`) return } - return this.$store.getters[val] + return this.$store.getters[nsVal] } // mark vuex getter for devtools res[key].vuex = true @@ -95,7 +105,7 @@ export const mapActions = normalizeNamespace((namespace, actions) => { // get dispatch function from store let dispatch = this.$store.dispatch if (namespace) { - const module = getModuleByNamespace(this.$store, 'mapActions', namespace) + const module = getModuleByNamespace(this, 'mapActions', namespace) if (!module) { return } @@ -141,7 +151,9 @@ function normalizeMap (map) { */ function normalizeNamespace (fn) { return (namespace, map) => { - if (typeof namespace !== 'string') { + if (typeof namespace === 'function') { + // no-op + } else if (typeof namespace !== 'string') { map = namespace namespace = '' } else if (namespace.charAt(namespace.length - 1) !== '/') { @@ -151,6 +163,22 @@ function normalizeNamespace (fn) { } } +/** + * Evaluate an instance-specific namespace function to determine the runtime namespace + * @param {Object} vm component vm + * @param {Function|String} namespace + * @return {String} namespace + */ +function getInstanceSpecificNamespace (vm, namespace) { + if (typeof namespace === 'function') { + namespace = namespace.call(vm, vm) + if (namespace.charAt(namespace.length - 1) !== '/') { + namespace += '/' + } + } + return namespace +} + /** * Search a special module from store by namespace. if module not exist, print error message. * @param {Object} store @@ -158,8 +186,9 @@ function normalizeNamespace (fn) { * @param {String} namespace * @return {Object} */ -function getModuleByNamespace (store, helper, namespace) { - const module = store._modulesNamespaceMap[namespace] +function getModuleByNamespace (vm, helper, namespace) { + namespace = getInstanceSpecificNamespace(vm, namespace) + const module = vm.$store._modulesNamespaceMap[namespace] if (process.env.NODE_ENV !== 'production' && !module) { console.error(`[vuex] module namespace not found in ${helper}(): ${namespace}`) } diff --git a/test/unit/helpers.spec.js b/test/unit/helpers.spec.js index 1bc0fc409..244678258 100644 --- a/test/unit/helpers.spec.js +++ b/test/unit/helpers.spec.js @@ -94,6 +94,40 @@ describe('Helpers', () => { expect(vm.value.b).toBeUndefined() }) + it('mapState (with instance-specific namespace function)', () => { + const store = new Vuex.Store({ + modules: { + foo: { + namespaced: true, + state: { a: 1 }, + getters: { + b: state => state.a + 1 + } + } + } + }) + const vm = new Vue({ + store, + data () { + return { + moduleName: 'foo' + } + }, + computed: mapState(vm => vm.moduleName, { + a: (state, getters) => { + return state.a + getters.b + } + }) + }) + expect(vm.a).toBe(3) + store.state.foo.a++ + expect(vm.a).toBe(5) + store.replaceState({ + foo: { a: 3 } + }) + expect(vm.a).toBe(7) + }) + it('mapMutations (array)', () => { const store = new Vuex.Store({ state: { count: 0 }, @@ -206,6 +240,37 @@ describe('Helpers', () => { expect(store.state.foo.count).toBe(43) }) + it('mapMutations (with instance-specific namespace function)', () => { + const store = new Vuex.Store({ + modules: { + foo: { + namespaced: true, + state: { count: 0 }, + mutations: { + inc: state => state.count++, + dec: state => state.count-- + } + } + } + }) + const vm = new Vue({ + store, + data () { + return { + moduleName: 'foo' + } + }, + methods: mapMutations(vm => vm.moduleName, { + plus: 'inc', + minus: 'dec' + }) + }) + vm.plus() + expect(store.state.foo.count).toBe(1) + vm.minus() + expect(store.state.foo.count).toBe(0) + }) + it('mapGetters (array)', () => { const store = new Vuex.Store({ state: { count: 0 }, @@ -351,6 +416,47 @@ describe('Helpers', () => { expect(vm.count).toBe(9) }) + it('mapGetters (with instance-specific namespace function)', () => { + const store = new Vuex.Store({ + modules: { + foo: { + namespaced: true, + state: { count: 0 }, + mutations: { + inc: state => state.count++, + dec: state => state.count-- + }, + getters: { + hasAny: ({ count }) => count > 0, + negative: ({ count }) => count < 0 + } + } + } + }) + debugger + const vm = new Vue({ + store, + data () { + return { + moduleName: 'foo' + } + }, + computed: mapGetters(vm => vm.moduleName, { + a: 'hasAny', + b: 'negative' + }) + }) + expect(vm.a).toBe(false) + expect(vm.b).toBe(false) + store.commit('foo/inc') + expect(vm.a).toBe(true) + expect(vm.b).toBe(false) + store.commit('foo/dec') + store.commit('foo/dec') + expect(vm.a).toBe(false) + expect(vm.b).toBe(true) + }) + it('mapActions (array)', () => { const a = jasmine.createSpy() const b = jasmine.createSpy() @@ -461,6 +567,39 @@ describe('Helpers', () => { expect(a.calls.argsFor(0)[1]).toBe('foobar') }) + it('mapActions (with instance-specific namespace function)', () => { + const a = jasmine.createSpy() + const b = jasmine.createSpy() + const store = new Vuex.Store({ + modules: { + foo: { + namespaced: true, + actions: { + a, + b + } + } + } + }) + const vm = new Vue({ + store, + data () { + return { + moduleName: 'foo' + } + }, + methods: mapActions(vm => vm.moduleName, { + foo: 'a', + bar: 'b' + }) + }) + vm.foo() + expect(a).toHaveBeenCalled() + expect(b).not.toHaveBeenCalled() + vm.bar() + expect(b).toHaveBeenCalled() + }) + it('createNamespacedHelpers', () => { const actionA = jasmine.createSpy() const actionB = jasmine.createSpy()