Getting started with Vuex
State management is all the rage. Having a centralized, reactive datastore for your webapp makes everything easier, regardless of the size of you app. I will concede that not every situation calls for state management. For instance, a true single page app that has picture of your cat probably doesn’t need Vuex. Let’s get started.
Getting up and going is super easy. Start by installing Vuex, of course:
npm install vuex
From here, all you need to do is create a store file and attach Vuex to the Vue instance:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex)
Now we are ready to set up the boilerplate that Vuex requires. I’ll give you a little run-down on the concepts.
state – The state object holds the structure of your global state. Every object you may mutate within the life of your app. Make sure you declare it here, otherwise Vue won’t know to watch it for mutations. This key/value object can be preloaded with default state values, which you can use to your advantage such as knowing whether an initial fetch has been completed. Make sure to never mutate this object using means aside from Vuex.
mutations – This is the only object that ought to have direct mutating contact with the state. This is basically a setter function (Circa 2003), so use it that way only. I prefer to make these functions as atomic as possible. They should have a very specific purpose, such as setting a string value.
actions – This is the only contact your code ought to have with Vuex. These functions are kicked off (AKA dispatched), by your logic in order to perform complex actions, such as performing a GET request for data. The result of an action should end in a call to a mutation of the state.
getters – These are essentially transform layer that can be used to manipulate the state values as they stream to your logic. Use these to hot-convert values, or perform some sort of statistic on a state value.
vuex.dispatch – This is the way your logic interacts with the state instance. You dispatch an action to be performed, optionally supplying data as a second argument.
vuex.commit – This is a Vuex-internal communication method for calling mutation functions. Although you can call commit from your logic, I recommend against it, as it adds code paths that are hard to follow.
Let’s look at an example!
export default new Vuex.Store({ state: { magicInformation: 'This is the default data.' }, mutations: { setMagicInformation (context, value) { context.magicInformation = value } }, getters: { magicInformationLength(context) { // Return a transform of the magicInformation data let len = context.magicInformation.length return len ? len : 'No Data' } }, actions: { getMagicInformation (context) { return new Promise((resolve, reject) => { // Pretend we are calling a slow API setTimeout(() => { // Update the state with the proper method context.commit('setMagicInformation', 'New Information!') resolve() }, 3000) }) } } })
Here we have a Vuex store that simulates an asynchronous fetch to some API. This is simulated with a setTimeout, but you get the idea. Although not required, I prefer to return a Promise to the caller of the dispatch, so that, if needed, the app logic can know when a call has finished. After the timeout expires, the new data is committed to the state via the mutate function, which simply sets the state object. Pretty simple stuff.
One thing that might seem confusing is the first argument of actions and mutations; context. This is essentially the Vuex instance (this), which is automatically provided by Vuex, so you never have to worry about where it comes from.
Ok, so, how do we use this thing?!
import store from './store' export default { name: 'app', data() { return { status: 'Loading...' } }, computed: { magicInfo() { // Returns the dynamic value of magicInformation return store.state.magicInformation }, magicInfoLength() { // Returns the dynamic transformed value of magicInformation's length return store.getters.magicInformationLength } }, mounted() { // Simulate slow loading setTimeout(() => { // Let the user know we are attempting to fetch this.status = 'Fetching some data!' store.dispatch('getMagicInformation').then(() => { // Let the user know the fetch was successful this.status = 'Data Fetched!' }).catch(() => { // Oh know... this.status = 'Error Fetching Data' }) }, 2000) } }
This component imports the store and uses it to dispatch actions against it, and dynamically update the view when the state changes. There is no need to “watch” for state changes. Using the computed object, any state changes, related to the state object it returns will update the view, like magic. Getters work the same way, except they don’t necessarily reflect the stat itself, but rather a transform of it. For local state stuff (like, the loading progress of the view), I prefer to use the standard data object. I don’t think data like this ought to hang out in the global state, as you will end up adding lots of watchers to Vuex, with no added benefit to the other components within your app.
All the code is on my github, if you’d like to clone it and get started with Vuex quickly.
You can find that here (https://github.com/coolcorey/vuex-tutorial)
Good Luck, and Gosh Speed.