State
The centralized store instance is injected into all child components, providing reactive access to the state object. For instance, defining an inventory property:
export default new Vuex.Store({
state: {
inventory: 50
}
})Components can retrieve this value via computed properties:
export default {
computed: {
stockLevel() {
return this.$store.state.inventory;
}
}
}mapState
When a component needs to access multiple state properties, declaring individual computed properties becomes verbose. The mapState helper streamlines this process.
import { mapState } from 'vuex';Using the spread operator allows combining local computed properties with mapped state properties seamlessly:
computed: {
localCalc() {
return this.baseValue * 2;
},
...mapState({
stock: 'inventory',
combinedStock(state) {
return state.inventory + this.warehouseReserve;
}
})
}Getters
Getters act as computed properties for the store. They are useful for deriving or filtering state before returning it to components. Consider a state containing an array of products:
state: {
products: [
{ id: 1, name: 'Laptop', inStock: true },
{ id: 2, name: 'Mouse', inStock: false },
{ id: 3, name: 'Keyboard', inStock: true }
]
}To filter available products and calculate their count, define getters:
getters: {
availableItems(state) {
return state.products.filter(item => item.inStock);
},
availableCount(state, getters) {
return getters.availableItems.length;
}
}mapGetters
The mapGetters helper maps store getters to local computed properties:
import { mapGetters } from 'vuex';
computed: {
localProp() {
return 'Some local data';
},
...mapGetters([
'availableItems'
]),
...mapGetters({
itemCount: 'availableCount'
})
}Mutations
State modifications must be performed within mutations to ensure traceability. Mutations are synchronous functions that receive the state as the first argument.
mutations: {
RESTOCK(state) {
state.inventory++;
},
DEPLETION(state) {
state.inventory--;
}
}Components trigger mutations using commit:
methods: {
addItem() {
this.$store.commit('RESTOCK');
}
}Payloads
Additional data can be passed to mutations via a payload. It is recommended to use objects for payloads to improve readability:
mutations: {
SET_INVENTORY(state, payload) {
state.inventory += payload.amount;
}
}
// Component invocation
this.$store.commit('SET_INVENTORY', { amount: 20 });
// Object-style commit
this.$store.commit({ type: 'SET_INVENTORY', amount: 20 });Reactivity Rules
When adding new properties to state objects, Vue's reactivity system requires specific handling. Always initialize state properties upfront. For new properties, use Vue.set() or replace the entire object using the spread operator:
mutations: {
ADD_DISCOUNT(state) {
// Vue.set(state, 'discountTag', 'SALE');
state.config = { ...state.config, discountTag: 'SALE' };
}
}Synchronous Requirement
Mutations must remain synchronous. Asynchronous logic inside a mutation breaks state tracking:
// Incorrect approach
mutations: {
BAD_MUTATION(state) {
api.callAsync(() => {
state.inventory++;
});
}
}mapMutations
Mapping mutations simplifies component methods:
import { mapMutations } from 'vuex';
methods: {
...mapMutations([
'RESTOCK', // maps to this.$store.commit('RESTOCK')
'SET_INVENTORY' // maps to this.$store.commit('SET_INVENTORY', payload)
]),
...mapMutations({
deplete: 'DEPLETION' // maps to this.$store.commit('DEPLETION')
})
}Actions
Actions handle asynchronous operations. Instead of mutating state directly, they commit mutations. An action receives a context object exposing the same methods and properties as the store instance.
state: {
shipmentStatus: 'pending'
},
mutations: {
UPDATE_STATUS(state, payload) {
state.shipmentStatus = payload.status;
}
},
actions: {
processShipment(context, payload) {
const finalStatus = 'shipped-' + payload.priority;
setTimeout(() => {
context.commit('UPDATE_STATUS', { status: finalStatus });
}, 1500);
}
}Components dispatch actions:
methods: {
shipOrder() {
this.$store.dispatch('processShipment', { priority: 'express' });
// Object-style dispatch
this.$store.dispatch({ type: 'processShipment', priority: 'standard' });
}
}Composing Actions with Promises
Actions naturally return Promises, enabling sequential execution:
actions: {
actionA({ commit }) {
return new Promise((resolve) => {
setTimeout(() => {
commit('SOME_MUTATION');
resolve();
}, 1000);
});
},
actionB({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('ANOTHER_MUTATION');
});
}
}mapActions
import { mapActions } from 'vuex';
methods: {
...mapActions([
'processShipment' // maps to this.$store.dispatch('processShipment')
]),
...mapActions({
ship: 'processShipment' // maps to this.$store.dispatch('processShipment')
})
}Modules
To prevent a monolithic store, Vuex allows dividing the store into modules. Each module possesses its own state, mutations, actions, and getters.
const userModule = {
state: { username: 'admin', role: 'super' },
mutations: { /* ... */ }
};
const productModule = {
state: { itemName: 'Widget', price: 9.99 },
mutations: { /* ... */ }
};
export default new Vuex.Store({
modules: {
user: userModule,
product: productModule
}
});Module state is accessed based on the registration key:
computed: {
currentUser() {
return this.$store.state.user.username;
}
}Namespaced Modules
By default, mutations, actions, and getters are registered globally. Adding namespaced: true isolates them based on their module path.
const cartModule = {
namespaced: true,
state: { items: [10, 20, 30, 40] },
getters: {
premiumItems(state) {
return state.items.filter(i => i > 25);
}
},
mutations: {
ADD_ITEM(state, payload) {
state.items.push(payload.val);
}
},
actions: {
// context.state and context.rootState available
}
};
export default new Vuex.Store({
modules: { cart: cartModule }
});Namespaced mutations require the path prefix:
this.$store.commit('cart/ADD_ITEM', { val: 50 });Accessing Global Assets in Namespaced Modules
Within namespaced modules, getters receive rootState and rootGetters as third and fourth arguments. Actions receive rootGetters and can dispatch or commit globally using { root: true }.
const nestedModule = {
namespaced: true,
getters: {
combinedGetter(state, getters, rootState, rootGetters) {
return state.value + rootState.globalValue;
}
},
actions: {
globalAction({ dispatch, commit }) {
dispatch('someGlobalAction', null, { root: true });
commit('SOME_GLOBAL_MUTATION', null, { root: true });
}
}
};Namespaced Module Helpers
When using mapping helpers with namespaced modules, pass the namespace string as the first argument:
computed: {
...mapState('cart', ['items'])
},
methods: {
...mapMutations('cart', ['ADD_ITEM'])
}Alternatively, use createNamespacedHelpers to generate bound helpers:
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapMutations } = createNamespacedHelpers('cart');