I have this Vuex module:
//modules/things.js const state = { firstThing: 'abc', secondThing: 'def', }; const getters = { getFirstThing: state => state.firstThing, getSecondThing: state => state.secondThing, }; const mutations = { setFirstThing: (state, payload) => state.firstThing = payload, setSecondThing: (state, payload) => state.secondThing = payload }; const actions = {}; export default { namespaced: true, // <------ state, mutations, actions, getters };
I use namespaced: true
flag and can work with this module like this:
this.$store.state.things.firstThing // <-- return abc here this.$store.commit('things/setFirstThing', 10) this.$store.getters['things/getFirstThing'] // <-- return abc here
If I will use constants like in Vuex official example, and refactor my modules/things.js
file like this:
export const Types = { getters: { GET_FIRST_THING: 'GET_FIRST_THING', GET_SECOND_THING: 'GET_SECOND_THING', }, mutations: { SET_FIRST_THING: 'SET_FIRST_THING', SET_SECOND_THING: 'SET_SECOND_THING', } }; const getters = { [Types.getters.GET_FIRST_THING]: state => state.firstThing, [Types.getters.GET_SECOND_THING]: state => state.secondThing, }; const mutations = { [Types.mutations.SET_FIRST_THING]: (state, payload) => state.firstThing = payload, [Types.mutations.SET_SECOND_THING]: (state, payload) => state.secondThing = payload };
I will have to use namespace prefix:
this.$store.commit('things/' + Types.mutations.SET_FIRST_THING, 10); this.$store.getters['things/' + + Types.getters.GET_FIRST_THING]
If I will include module namespace prefix to Types
constant, I will have to use string prefix things/
for mutations/actions/getters declaration:
const getters = { ['things/' + Types.getters.GET_FIRST_THING]: state => state.firstThing, ['things/' + Types.getters.GET_SECOND_THING]: state => state.secondThing, };
How to avoid that?
You can disable namespacing by namespaced: false
and just use constants with prefixes:
export const Types = { getters: { GET_FIRST_THING: 'THINGS_GET_FIRST_THING', // your namespace without '/' slash GET_SECOND_THING: 'THINGS_GET_SECOND_THING', }, // ... }
- it will work.
But if you still want to keep namespaced: true
in module and use constants also, you can define two types of constants: public and private:
export const Types = { // <-- public getters: { GET_FIRST_THING: 'things/GET_FIRST_THING', GET_SECOND_THING: 'things/GET_SECOND_THING', }, mutations: { SET_FIRST_THING: 'things/SET_FIRST_THING', SET_SECOND_THING: 'things/SET_SECOND_THING', } }; const _types = removeNamespace('things/', Types); // <-- private
Then use private _types
only inside Vuex module:
const getters = { [_types.getters.GET_FIRST_THING]: state => state.firstThing, [_types.getters.GET_SECOND_THING]: state => state.secondThing, }; //...
and public Types
outside module:
// some-component.vue this.$store.commit(Types.mutations.SET_FIRST_THING, 10); this.$store.getters[Types.getters.GET_FIRST_THING] // ...
Also implement simple removeNamespace
function in your new namespace-helper.js
file:
export default function removeNamespace(namespace, types){ return _.reduce(types, (typeObj, typeValue, typeName) => { typeObj[typeName] = _.reduce(typeValue, (obj, v, k)=>{ obj[k] = v.replace(namespace, ''); return obj; }, {}); return typeObj; }, {}); }
The answer from @hedin worked brilliantly for me, thank you!
The only issues I had were:
I'm using Typescript.
It could be a bit overly verbose, hurting readability. However type safety is more important to me, and I'm willing to tolerate some verbosity get type checking.
Inspired by his design, I Typescripted it and reduced the verbosity.
(I am using Vue 3 (with composition API) + Vuex 4 (with namespaced modules).)
Firstly, I created namespace-helper.ts
that looks like this:
import _ from "lodash"; type NamespaceHelper = { [name: string]: string; }; // Enhanced from @hedin, see https://stackoverflow.com/a/47646215/1360592 export default ( namespace: string, types: any, section: "getters" | "actions" | "mutations", ): NamespaceHelper => { return _.reduce( types, (typeObj: NamespaceHelper, typeValue, typeName) => { if (typeName === section) { return _.reduce( typeValue, (obj: NamespaceHelper, v, k) => { obj[k] = v.replace(namespace, ""); return obj; }, {}, ); } return typeObj; }, {}, ); };
Then in my store module, I have:
const namespace = "things"; // For external use export const Types = { getters: { GET_FIRST_THING: `${namespace}/GET_FIRST_THING`, GET_SECOND_THING: `${namespace}/GET_SECOND_THING`, }, actions: { DO_FIRST_THING: `${namespace}/DO_FIRST_THING`, DO_SECOND_THING: `${namespace}/DO_SECOND_THING`, }, mutations: { SET_FIRST_THING: `${namespace}/SET_FIRST_THING`, SET_SECOND_THING: `${namespace}/SET_SECOND_THING`, }, }; // For internal use in the same store const _getters = removeNamespace(`${namespace}/`, Types, "getters"); const _actions = removeNamespace(`${namespace}/`, Types, "actions"); const _mutations = removeNamespace(`${namespace}/`, Types, "mutations"); // getters const getters: GetterTree<MyStoreState, RootState> = { [_getters. GET_FIRST_THING]: (state) => { return state.blah; }, ... }; // actions const actions: ActionTree<MyStoreState, RootState> = { [_actions.DO_FIRST_THING]: ({ commit }) => { // do stuff here ... commit(_mutations.SET_FIRST_THING); }, }; // mutations const mutations = { [_mutations.SET_FIRST_THING]: (state: MyStoreState) => { state.blah = "foo"; }, }; export default { namespaced: true, state, getters, actions, mutations, };
And here is how I use it from a component:
<script lang="ts"> // imports go here, not shown for brevity import { Types } from "@/store/modules/things"; export default defineComponent({ name: "Thing", setup(props) { const store = useStore<RootState>(); // I prefer singular for consuming getters and actions externally. const { getters: getter, actions: action } = Types; const firstThing = computed<ThingType>(() => store.getters[getter.GET_FIRST_THING], ); store.dispatch(action.DO_FIRST_THING); return { firstThing, }; }, }); </script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With