An Introduction to Vuex
Vuex is the official state management library for Vue. A handy metaphor is that Vuex is to Vue as Redux is to React. If you already know Redux, Vuex will seem familiar, just with slightly different terminology. In this article, you'll learn the basics of Vuex from standalone Node.js scripts, no browser required.
First, to get started, you should install vue, vuex, and vue-server-renderer from npm. Here's how you import these libraries:
const { renderToString } = require('vue-server-renderer').createRenderer();
const Vuex = require('vuex');
Vue.use(Vuex);
Next, let's define a template that displays a single number count
that's stored in Vuex. This script has 4 steps:
Create a Vuex store. To create a Vuex store, you need to define state, mutations, and actions.
Create a Vue app that's wired up to use the Vuex store.
Render the app using vue-server-renderer.
Dispatch an action and re-render the app using vue-server-renderer.
/**
* Step 1: Create a Vuex store.
* Vuex stores have 3 primary concepts:
* - `state` is a POJO that contains all the application's data
* - `mutations` are synchronous functions that change the `state`
* - `actions` are potentially async functions that may trigger 1 or
* more mutations.
*/
const state = { count: 0 };
const mutations = {
increment: (state) => { ++state.count; },
decrement: (state) => { --state.count; }
};
const actions = {
increment: ({ commit }) => commit('increment'),
decrement: ({ commit }) => commit('decrement')
};
const store = new Vuex.Store({ state, mutations, actions });
// Step 2: Create a Vue app that's wired up to use the Vuex store
const app = new Vue({
store,
// In Vue templates, you reference the Vuex state with `$store.state`
template: '<div>{{$store.state.count}}</div>'
});
// Step 3: Render the app using vue-server-renderer
await renderToString(app); // <div data-server-rendered="true">0</div>
// Step 4: Dispatch an action and re-render the app
store.dispatch('increment');
store.state.count; // 1
await renderToString(app); // <div data-server-rendered="true">1</div>
assert.equal(await renderToString(app),
'<div data-server-rendered="true">1</div>');
// acquit:ignore:end
If you're coming from Redux, the concepts of state and action in Vuex are equivalent to states and actions in Redux. You can think of a mutation as being equivalent to a reducer.
Async Actions
One key difference between actions and mutations is that actions can be asynchronous, whereas mutations must be synchronous. Making state changes in separate synchronous mutations enables better debugging and better devtools. Actions, however, can be async. For example, your increment
action can be async as shown below.
// Create the store
const state = { count: 0 };
const mutations = {
increment: (state) => { ++state.count; },
decrement: (state) => { --state.count; }
};
const actions = {
increment: async ({ commit }) => {
await new Promise(resolve => setTimeout(resolve, 100));
commit('increment');
}
};
const store = new Vuex.Store({ state, mutations, actions });
// Create the app
const app = new Vue({
store,
template: '<div>{{$store.state.count}}</div>'
});
// Dispatch an action. Note that the `dispatch()` function returns a
// promise because the `increment` action is an async function.
await store.dispatch('increment');
await renderToString(app); // <div data-server-rendered="true">1</div>
assert.equal(await renderToString(app),
'<div data-server-rendered="true">1</div>');
// acquit:ignore:end
One important caveat is that Vuex doesn't handle errors in async actions for you. If an async action throws an error, you'll get an unhandled promise rejection unless you explicitly handle the error using .catch()
or async/await.
const actions = {
increment: async () => {
await new Promise(resolve => setTimeout(resolve, 100));
throw new Error('Oops');
}
};
const store = new Vuex.Store({ state, mutations, actions });
//
const err = await store.dispatch('increment').catch(err => err);
err.message; // "Oops"