Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.
With Vuex, we can store our Vue app’s state in a central location.
In this article, we’ll look at how to add modules to separate a Vuex store into smaller parts.
Dividing a Store into Modules
Vuex uses a single state tree. This means the states are located in one big object. This will be bloated is our app grows big.
To make a Vuex store easier to scale, it can be separated into modules. Each module can have its own state, mutations, getters, and actions.
The state
parameter in mutations and getters are the module’s local state.
By default, all actions, mutations, and getters inside modules are registered under a global namespace. This allows multiple modules to react to the same mutation or action type.
We can divide our store into module as in the following example:
index.js
:
const moduleA = {
state: {
count: 0
},
mutations: {
increase(state, payload) {
state.count += payload.amount;
}
},
actions: {
increase({ commit }, payload) {
commit("increase", payload);
}
}
};
const moduleB = {
state: {
count: 1
},
mutations: {
increase(state, payload) {
state.count += payload.amount;
}
},
actions: {
increase({ commit }, payload) {
commit("increase", payload);
}
}
};
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
});
console.log(store.state.a.count);
console.log(store.state.b.count);
Then in the console.log
output, we should see 0 and 1 since moduleA
‘s initial count
state is 0 and moduleB
‘s initial count
state is 1.
To make each module self-contained, we have to namespace it by setting the namespaced
option to true
.
We can namespace a module and then call dispatch
on actions after namespacing the modules as follows:
index.js
:
const moduleA = {
namespaced: true,
state: {
count: 0
},
mutations: {
increase(state, payload) {
state.count += payload.amount;
}
},
actions: {
increase({ commit }, payload) {
commit("increase", payload);
}
}
};
const moduleB = {
namespaced: true,
state: {
count: 1
},
mutations: {
increase(state, payload) {
state.count += payload.amount;
}
},
actions: {
increase({ commit }, payload) {
commit("increase", payload);
}
}
};
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
});
new Vue({
el: "#app",
store,
computed: {
...Vuex.mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
increaseA(payload) {
this.$store.dispatch("a/increase", payload);
},
increaseB(payload) {
this.$store.dispatch("b/increase", payload);
}
}
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<button @click="increaseA({amount: 10})">Increase</button>
<button @click="increaseB({amount: 10})">Increase</button>
<p>A Count: {{a.count}}</p>
<p>B Count: {{b.count}}</p>
</div>
<script src="index.js"></script>
</body>
</html>
In the code above, we have namespaced: true
set in each module, and then we added 2 methods to our Vue instance to dispatch the namespaced actions:
increaseA(payload) {
this.$store.dispatch("a/increase", payload);
}
and:
increaseB(payload) {
this.$store.dispatch("b/increase", payload);
}
Since we have namespaced
set to true
, we have to dispatch the actions by passing in “a/increase”
and “b/increase”
to dispatch
.
Then once we clicked the buttons, our methods are called to dispatch the actions and the numbers will increase.
Register Global Action in Namespaced Modules
We can also register global actions in namespaced modules, by setting the root
option to true
and place the action definition in the function handler.
To do register a global action, we can write something like the following code:
index.js
:
const moduleA = {
namespaced: true,
state: {
count: 0
},
mutations: {
increase(state, payload) {
state.count += payload.amount;
}
},
actions: {
increase({ commit }, payload) {
commit("increase", payload);
}
}
};
const moduleB = {
namespaced: true,
state: {
count: 1
},
mutations: {
increase(state, payload) {
state.count += payload.amount;
}
},
actions: {
increase({ commit }, payload) {
commit("increase", payload);
}
}
};
const rootModule = {
actions: {
increaseAll: {
root: true,
handler(namespacedContext, payload) {
namespacedContext.commit("a/increase", payload);
namespacedContext.commit("b/increase", payload);
}
}
}
};
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB,
root: rootModule
}
});
new Vue({
el: "#app",
store,
computed: {
...Vuex.mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
...Vuex.mapActions(["increaseAll"])
}
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<button @click="increaseAll({amount: 10})">Increase All</button>
<p>A Count: {{a.count}}</p>
<p>B Count: {{b.count}}</p>
</div>
<script src="index.js"></script>
</body>
</html>
In the code above, we added the rootModule
which has the global action as follows:
const rootModule = {
actions: {
increaseAll: {
root: true,
handler(namespacedContext, payload) {
namespacedContext.commit("a/increase", payload);
namespacedContext.commit("b/increase", payload);
}
}
}
};
A global action has the root
option set to true
and a handler
method which is used to dispatch actions from any module.
Then in the Vue instance, we have:
methods: {
...Vuex.mapActions(["increaseAll"])
}
to map the increaseAll
action to a method in the Vue instance.
Then in the template, we have:
<button @click="increaseAll({amount: 10})">Increase All</button>
to call the increaseAll
method returned from the mapActions
method when the button is clicked.
Then we should both numbers increasing since we mapped both module’s state to the Vue instance’s data.
Dynamic Module Registration
We can also register a module dynamically by using the store.registerModule
method as follows:
index.js
:
const moduleA = {
state: {
count: 0
},
mutations: {
increase(state, payload) {
state.count += payload.amount;
}
},
actions: {
increase({ commit }, payload) {
commit("increase", payload);
}
}
};
const store = new Vuex.Store({});
store.registerModule("a", moduleA);
new Vue({
el: "#app",
store,
computed: {
...Vuex.mapState({
a: state => state.a
})
},
methods: {
...Vuex.mapActions(["increase"])
}
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
</head>
<body>
<div id="app">
<button @click="increase({amount: 10})">Increase</button>
<p>A Count: {{a.count}}</p>
</div>
<script src="index.js"></script>
</body>
</html>
The store.registerModule
takes a string for the module name, and then an object for the module itself.
Then we can call the helpers to map getters to computed properties and actions/mutations to methods. In the code above, we have the increase
action mapped to a method.
Then we can call it in our template as usual.
Conclusion
If our Vuex store is big, we can divide it into modules.
We can register modules when we create the store or dynamically with registerModule
.
Then we can map actions/mutations by their name as usual, and we can map the state by accessing state.a.count
, where a
is the module name, and count
is the state name. Replace it with our own module and state names if the code is different.
We can also namespace the modules. Then we dispatch the actions starting with the module name and a slash instead of just the name.