Architecture and Data Flow
Vuex functions as a centralized store for Vue applications, enforcing a strict, unidirectional state management pattern. Data mutations follow a predictable pipeline:
- Component triggers
$store.dispatch()to invoke an Action. - Action executes business logic or handles asynchronous operations, then commits a Mutation.
- Mutation synchronously updates the State.
- Components reactively read the updated state.
Reading State with mapState
The foundational layer of the store holds reactive data accessible throughout the application tree. Direct access is possible via the global instance, but declarative binding improves readability.
import { mapState } from 'vuex';
export default {
computed: {
...mapState({
currentTheme: state => state.app.theme,
activeTab: state => state.navigation.selectedId
})
}
};
In the template, variables resolve automatically: {{ currentTheme }} and {{ activeTab }}.
Synchronous State Mutations
All state modifications must pass through explicitly defined mutations. This constraint enables time-travel debugging and strict development mode verification.
const store = new Vuex.Store({
strict: true,
state: {
databaseSize: 1024,
uiMode: 'compact'
},
mutations: {
resizeStorage(state, newSize) {
state.databaseSize = newSize;
},
switchUiPattern(state, pattern) {
state.uiMode = pattern;
}
}
});
Components trigger these methods via the commit interface:
methods: {
handleResize(event) {
this.$store.commit('resizeStorage', event.target.value);
},
toggleLayout() {
this.$store.commit('switchUiPattern', 'expanded');
}
}
For cleaner method signatures, map mutations directly into the component scope:
methods: {
...mapMutations(['resizeStorage', 'switchUiPattern']),
applyExpansion() {
this.switchUiPattern('expanded');
}
}
Handling Async Operations with Actions
While mutations remain synchronous, actions serve as intermediaries for non-blocking tasks such as API fetches, delays, or complex event batching. Actions receive a context object granting access to commit functions without directly touching state.
actions: {
initializeDashboard(context, configData) {
return new Promise((resolve) => {
setTimeout(() => {
context.commit('configureView', configData);
resolve();
}, 2000);
});
}
}
Dispatch actions from components:
methods: {
loadDashboard() {
this.$store.dispatch('initializeDashboard', { layout: 'grid' });
}
}
Helper mappings simplify invocation:
methods: {
...mapActions(['initializeDashboard']),
initApp() {
this.initializeDashboard({ layout: 'grid' });
}
}
Computed Properties with Getters
Similar to mapState, derived calculations reside in computed sections. Getters optimize performance by caching results based on underlying dependencies.
getters: {
doubledCapacity: (state) => state.storage.current * 2,
highPriorityTasks: (state) => state.tasks.filter(t => t.priority === 'high')
}
Expose them alongside other computed properties:
computed: {
...mapGetters(['doubledCapacity', 'highPriorityTasks'])
}
Modular Namespacing
Large applications benefit from splitting the single state tree into isolated feature modules. Enabling namespaced: true prevents naming collisions and localizes state interactions.
const analyticsModule = {
namespaced: true,
state: () => ({ eventLogs: [], trackingEnabled: false }),
mutations: {
recordInteraction(state, logEntry) {
state.eventLogs.push(logEntry);
},
toggleTracking(state, flag) {
state.trackingEnabled = flag;
}
},
actions: {
persistLog(context, payload) {
setTimeout(() => {
context.commit('recordInteraction', payload);
}, 500);
}
}
};
Namespace-aware helpers require explicit path references during registration:
methods: {
...mapMutations('analyticsModule', ['recordInteraction', 'toggleTracking']),
...mapActions('analyticsModule', ['persistLog'])
}
// Access in template
// {{ $store.state.analyticsModule.eventLogs.length }}