Vuex centralizes application state into a single store, making data flow predictable. The store is defined with an initial state, mutations for synchronous updattes, actions for asynchronous work, getters for derived values, and modules for scalability.
Accessing State
Create a store instance (src/store/index.js):
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0,
message: 'Hello',
items: [3, 8, 1, 9, 4]
}
})
Retrieve state in components using this.$store.state or the mapState helper:
<template>
<div>
<p>{{ $store.state.counter }}</p>
<p>{{ counter }} - {{ message }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['counter', 'message'])
},
created() {
console.log(this.$store.state.counter)
}
}
</script>
Synchronous Updates with Mutations
Mutations are the only way to change state; they must be synchronous. Register them in the store:
const store = new Vuex.Store({
state: {
counter: 0,
message: 'Hello',
items: [3, 8, 1, 9, 4]
},
mutations: {
adjustCounter(state, delta) {
state.counter += delta
}
}
})
Trigger mutations with commit:
this.$store.commit('adjustCounter', 15)
Use mapMutations to simplify method binding:
<button @click="performChange">Change</button>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
computed: {
...mapState(['counter'])
},
methods: {
...mapMutations(['adjustCounter']),
performChange() {
this.adjustCounter(25)
}
},
created() {
console.log(this.$store.state.counter)
this.$store.commit('adjustCounter', 15)
console.log(this.$store.state.counter)
}
}
</script>
Handling Asynchornicity with Actions
Actions can contain async operations and commit mutations. The first paarmeter is a context object exposing commit, dispatch, etc.
const store = new Vuex.Store({
// ... state, mutations ...
actions: {
fetchAndAdd({ commit }, payload) {
// simulate async work
setTimeout(() => {
commit('adjustCounter', payload.amount)
}, 500)
}
}
})
Dispatch actions using this.$store.dispatch or mapActions:
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
methods: {
...mapMutations(['adjustCounter']),
...mapActions(['fetchAndAdd']),
runAsync() {
this.fetchAndAdd({ amount: 30 }) // shorthand
}
},
created() {
console.log(this.$store.state.counter)
this.$store.dispatch('fetchAndAdd', { amount: 50 }) // full syntax
console.log(this.$store.state.counter)
}
}
Deriving Data with Getters
Getters compute derived state, similar to computed properties for the store.
const store = new Vuex.Store({
// ... state ...
getters: {
filteredLargeItems(state) {
return state.items.filter(item => item > 5)
}
}
})
Access getters via $store.getters or mapGetters:
<p>{{ $store.getters.filteredLargeItems }}</p>
<p>{{ filteredLargeItems }}</p>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState(['counter', 'message']),
...mapGetters(['filteredLargeItems'])
}
}
</script>
Modularizing the Store
As the application grows, organize code into modules with their own state, mutations, actions, and getters. Enable namespacing (namespaced: true) to avoid name collisions.
Module file: modules/cart.js
const state = {
items: [],
totalPrice: 0
}
const mutations = {
addToCart(state, product) {
state.items.push(product)
state.totalPrice += product.price
}
}
const actions = {
asyncCheckout({ commit }, payload) {
// perform async checkout
commit('addToCart', payload)
}
}
const getters = {
itemCount(state) {
return state.items.length
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
Root store index.js
import Vue from 'vue'
import Vuex from 'vuex'
import cart from './modules/cart'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 0,
message: 'Hello',
items: [3, 8, 1, 9, 4]
},
mutations: {
adjustCounter(state, delta) {
state.counter += delta
}
},
actions: {
fetchAndAdd({ commit }, payload) {
commit('adjustCounter', payload.amount)
}
},
getters: {
filteredLargeItems(state) {
return state.items.filter(item => item > 5)
}
},
modules: {
cart
}
})
Using namespaced module state and getters in components
<template>
<div>
<!-- raw access -->
<p>{{ $store.state.cart.items }}</p>
<p>{{ $store.getters['cart/itemCount'] }}</p>
<!-- mapped access -->
<p>{{ items }}</p>
<p>{{ itemCount }}</p>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState('cart', ['items']),
...mapGetters('cart', ['itemCount'])
}
}
</script>
Committing mutations and dispatching actions with namespacing
<script>
import { mapMutations, mapActions } from 'vuex'
export default {
methods: {
...mapMutations('cart', ['addToCart']),
...mapActions('cart', ['asyncCheckout']),
addProduct() {
// raw commit
this.$store.commit('cart/addToCart', { id: 2, price: 29.99 })
// mapped (shorthand)
this.addToCart({ id: 1, price: 49.99 })
},
processCheckout() {
// raw dispatch
this.$store.dispatch('cart/asyncCheckout', { id: 3, price: 15.50 })
// mapped
this.asyncCheckout({ id: 4, price: 99.00 })
}
}
}
</script>