Introduction
Vuex is a library that stores data in a Vuex store, which acts as the source of data on states in a Vue application. This store contains a global state (set of properties) and functions (getters, actions and mutations) used to read and alter the state.
Consider a scenario in which you want to want to create a simple Vue application that lets you add or remove a value from a variable, count
, like in the image below:
The normal process would be to - first, define a function that returns our count
variable:
data() {
return {
count: 0
}
}
And then define an increment, and decrement function so that we can manipulate the count
:
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
}
While there's nothing wrong with this pattern (at least in simple applications), an issue arises when you try to share the data between different components reactively.
You can choose to use component props
, but considering a realistic case of working with 50-100 (or more) components, using props would become tedious fairly quickly.
Sharing data between components becomes more complicated when developing large-scale applications, i.e., applications with dozens of components. This is why solutions like Vuex were created - to make state management less painful.
In this guide, we'll go over all you need to know to get started with Vue.js' official state management library, Vuex.
Getting Started with Vuex
Vuex was inspired by projects like Facebook's Flux and React's State Management Library, Redux, to make storing and exchanging reactive data across your Vue app as simple as possible while ensuring performance and maintainability.
This is done by having a centralized store that you draw data from, and write data to. There's no other way to obtain or alter it - making it consistent and stable over many components. This removes the instability that can oftentimes be caused when multiple reactive components talk to each other.
According to its documentation:
"Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion."
This is achieved through three types of methods, operating on the recorded state. Getters are used to get data from the store, actions are used to asynchronously fetch data, process it and invoke mutations, and mutations are used to change the source data in the store. In a sense, you can imagine a cycle of:
Centralized, global state (or property of it) is displayed to the user through getters -> application invokes an action -> action invokes a mutation on the global state -> getters are used to retrieve the new state and display it to the user.
Through these elements - you can perform stable, maintainable state management.
Installing Vuex
There are several ways to install Vuex - most of them being dependent on how you created your vue app.
Note: If you are already familiar with the Vuex installation process, you can skip this section and go to the Centralized State Management with Vuex section.
If your project uses the Vue CDN rather than downloaded tools like vue-cli
or vite
, you'll want to download the the Vuex source file and include it in your application markup:
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>
If you're using the vue-cli, you can directly include official Vue packages like vue-router
and vuex
during the installation process.
First, you'll want to create a new Vue project:
$ vue create project-name
Running this should bring out the following output:
From here, you can use your keyboard to navigate to the Manually select features option, to add all of the packages your app will need, including Vuex:
Alternatively, if you have an existing project created using vue-cli
or Vite that does not have prior support for Vuex, you can install it with npm
or yarn
:
$ npm install vuex --save
# Or yarn
$ yarn add vuex
Vuex Configuration
If you installed Vuex as a package with yarn
or npm
, you must explicitly instruct Vue to use it as a plugin:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
new Vue({...})
Centralized State Management with Vuex
The Store is the center of all operations in a Vuex application. It's a reactive container that holds the state of your entire application as well as functions required to read from and write to this state set. Furthermore, data or methods defined in a store are universal, meaning you can access them from anywhere in your vue app.
We can easily create a new Vuex store by initializing it using the imported Vuex
:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {},
mutations: {},
actions: {},
getters: {}
});
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
While it's empty - the store just has a collection of empty states, mutations, actions and getters. We can access this store instance through a global this.$store
instance! Though, before accessing it, we'll want to register it in our Vue instance:
new Vue({
el: '#app',
store: store,
})
Now, we can populate the store with states and functions that'll make it useful for us!
Vuex State
Consider the Vuex state to be the equivalent of data
in a Vue instance. However, unlike data
, everything you store in a state is global - meaning, it is not restricted to a single component and can be accessed or modified anywhere in your application.
You can add entries to the state by simply adding properties to the state
field of the store instance:
const store = new Vuex.Store({
state: {
name: "John Doe",
age: 12,
details: {
city: "San Fransisco",
country: "USA",
},
},
});
Here, we defined a state for username
, age
, and details
(an object containing the user's city
and country
). These properties of the state
are now globally accessible!
Typically, you'd define some global constants here.
Accessing Vuex States
The simplest way to retrieve a state value is to return the state from within a computed property. Let's say we want to access the global name
and age
state in our store:
computed: {
name() {
return this.$store.state.name;
},
age() {
return this.$store.state.age;
},
}
Note: Declaring all of these computed properties might become tedious and verbose when a component needs to access several store states. There's a helper class created just for that which we'll cover in a minute!
Of course, these values can now be accessed in our markup:
<template>
<div id="app">
<p>Name: {{ name }}</p>
<p>Age: {{ age }}</p>
</div>
</template>
The mapState() Helper
Declaring computed properties, as seen in the previous example, may become very lengthy. Anticipating this, Vuex ships with a helper for generating computed getter functions. It's a state mapper and allows you to easily map computed states to shorter aliases.
For instance, let's import the mapState()
helper function and map the state.name
and state.age
to shorter aliases:
// First - import mapState
import { mapState } from "vuex";
export default {
name: "ComponentName",
computed: mapState({
name: (state) => state.name,
age: (state) => state.age,
}),
};
Getter Functions
Getter functions are functions used to get a computed property from a store. While getting a property - you may choose to additionally filter, validate or manipulate the data if need be, before returning it.
Note: Getters are used only to get data, and not modify the original source. While you can filter and manipulate what you get back to the user, you mustn't change the original source in-place.
For example, say there's a state that keeps track of integers. Through a getter function, you may return the numbers as they are, or sort them and slice away some number from them to return:
const store = new Vuex.Store({
state: {
myNumbers: [11, 3, 5, 1, 54, 56, ...],
},
getters: {
firstFiveSorted: (state) => {
return state.myNumbers.sort().slice;
},
},
});
This getter is now available in the store.getters
global object, and can also be accessed inside any component:
//...
computed: {
firstFiveSorted () {
return this.$store.getters.firstFiveSorted
}
}
Though, this also gets verbose after a while. Just like you can map states to their aliases, you can also map getters to their own aliases, through a mapGetters()
helper function:
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
// this example will map `myNumbers` to `this.$store.getters.firstFiveSorted`
myNumbers: "firstFiveSorted",
}),
},
};
Vuex Mutations
In Vuex, the only way to modify a source state is through mutations. You might think of them as the methods
property in a Vue instance, but serve to modify a state in a Vuex store. Additionally, mutations are carried out through the store, to ensure that changes are predictable.
Note: By convention, mutation names are capitalized, and stylized with SNAKE_CASE
.
Mutations will receive the state as the first argument, and an optional payload (i.e optional arguments required to commit the mutation) as the second:
const store = new Vuex.Store({
state: {
myNumbers: [11, 3, 5, 1, 54, 56]
},
mutations: {
ADD_NUMBER(state, numberToAdd) {
state.myNumbers.push(numberToAdd);
},
}
})
And, in order to invoke a mutation, we'll need to call the store.commit()
method with the mutation name and its payload, if a payload exists:
this.$store.commit('ADD_NUMBER', 75);
The only way to call mutations is to commit a change to the store, passing in the mutation's name.
Note: One downside to mutations is that they must be synchronous, i.e. you cannot perform an asynchronous operation within them. Vuex actions
are a solution to this, which we'll discuss in the following section.
Actions
Actions are used to fetch and process data before having a mutation commit that change. Additionally, you can asynchronously make multiple mutation calls through actions, while the mutations themselves then execute synchronously. Furthermore, actions can call other actions, unlike mutations which have to be invoked through the commit()
method.
In a sense - you can think of actions as enabling agents for mutating data, where mutations do the final heavy lifting.
Actions receive a context
object and an optional payload as their parameter. The context object provides access to methods such as context.commit()
, which allows you to commit a mutation. context.state()
, and context.getters()
allow you to access the store's state and getters. The context
object isn't the store instance - it just exposes the same properties as the store's instance.
For example, let's say we want to perform an asynchronous operation that adds a random number to the myNumber
array every n
seconds:
const store = new Vuex.Store({
state: {
myNumbers: [11, 3, 5, 1, 54, 56, "..."],
},
mutations: {
ADD_RANDOM_NUMBER(state) {
// Push a random number to `myNumbers` array
state.myNumbers.push(Math.floor(Math.random() * 10));
},
},
actions: {
// Using destructuring to extract only `commit()` from the `context` object
addNumber({ commit }, time) {
setInterval(() => {
commit("ADD_RANDOM_NUMBER");
}, time * 1000);
},
},
});
To invoke the action itself, we also call on the store to do that - via the dispatch()
function:
store.dispatch('ADD_RANDOM_NUMBER', 10);
Going through the store ensures that the state is only ever changed in a predictable manner.
Conclusion
In this guide, we've taken a look at Vuex - Vue's official State Management Store.
The Vuex Store is a collection of states, getters, mutations and actions. States are used to define global state properties, while getters are used to get them. You can map state properties and getters to shorter aliases to make it easier to reference them. Mutations and actions work hand-in-hand to alter the state properties in a controlled manner.
With this - you can apply Vuex to your next Vue project!