Vue State Management: Pinia vs Vuex
Pinia is now the official state management library recommended for Vue. It replaces Vuex as the primary store module, offering a simpler API, native TypeScript typing, and a modular architecture.
1. What the Interviewer Is Testing
Interviewers evaluate whether you can compare different state management strategies:
- The Store Architecture: Why does Pinia drop mutations in favor of actions?
- TypeScript Integration: How does Pinia resolve Vuex's typing limitations?
- Module Organization: The difference between Vuex's nested namespaces and Pinia's flat modules.
- DevTools Tracking: If Pinia has no mutations, how are state changes tracked and time-traveled in DevTools?
2. Vuex Store Structure (Traditional)
In Vuex, states can only be mutated inside Mutations, which must be synchronous. Actions perform asynchronous tasks and commit mutations.
// Traditional Vuex Store
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
// ONLY synchronous state changes allowed
INCREMENT(state) {
state.count++
}
},
actions: {
// Asynchronous work goes here
async incrementAsync({ commit }) {
await delay(1000)
commit('INCREMENT')
}
}
})The Vuex Typing Bottleneck
In Vuex, actions commit mutations using string identifiers (e.g. commit('INCREMENT')). This makes it extremely difficult for TypeScript compilers to trace parameter types, resulting in weak compile-time type safety.
3. Pinia Store Structure (Modern)
Pinia merges actions and mutations. Actions can update the state directly and perform both synchronous and asynchronous tasks.
// Modern Pinia Store
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
// Updates state directly and can run asynchronously
increment() {
this.count++
},
async incrementAsync() {
await delay(1000)
this.count++
}
}
})Setup-Store Pattern (Composition style)
Pinia also supports defining stores like standard composables, mapping refs to state, computed to getters, and functions to actions:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})4. Key Differences: Pinia vs Vuex
1. Goodbye Mutations
In Vuex, separating mutations and actions made it easy to track state changes in DevTools. Modern devtools hook directly into Pinia's getter/setter proxy triggers, allowing full tracking and time travel without needing a separate mutations wrapper.
2. Flat Modules vs Nested Namespaces
- Vuex: Uses a single global store tree with nested modules. Accessing nested actions requires string namespacing:
this.$store.dispatch('users/profiles/fetch'). - Pinia: Promotes importing separate modular stores directly into components. The module structure is flat, meaning imports are direct and typed:
const userStore = useUserStore().
5. Summary Matrix
| Metric | Vuex | Pinia |
|---|---|---|
| Structure | State, Getters, Mutations, Actions. | State, Getters, Actions (Mutations removed). |
| Type Safety | Weak (depends on magic string triggers). | Native (fully typed automatically). |
| Architecture | Single global store tree. | Flat modular imports. |
| Setup Options | Options API only. | Options API or Composition API (Setup Store). |
| Bundle Size | ~10kb. | ~1.5kb (highly tree-shakable). |
