Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for namespace functions in mapXXX helpers #1510

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the component may be loaded multiple times, the namespace of the subsequent component is always the result of the first execution, so the code cannot override namespace = namespace.call(this, this), we can use the local variable let ns = namespace.call(this, this)

if (namespace.charAt(namespace.length - 1) !== '/') {
namespace += '/'
}
}
const module = getModuleByNamespace(this, 'mapState', namespace)
if (!module) {
return
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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) !== '/') {
Expand All @@ -151,15 +163,32 @@ 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
* @param {String} helper
* @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}`)
}
Expand Down
139 changes: 139 additions & 0 deletions test/unit/helpers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down