Vue 3 Pinia vs Vuex : Gestion d'État Moderne et Questions d'Entretien 2026
Comparaison approfondie entre Pinia et Vuex : architecture, TypeScript, Composition API, performances, stratégies de migration et questions d'entretien Vue fréquentes en 2026.

La gestion d'état dans les applications Vue 3 a connu une transformation majeure avec l'arrivée de Pinia. Là où Vuex régnait en maître depuis les débuts de l'écosystème Vue, Pinia 3 s'impose désormais comme la solution officielle recommandée par l'équipe Vue. Vuex est en mode maintenance, sans version 5 prévue. Comprendre les différences entre ces deux bibliothèques est devenu incontournable pour tout développeur Vue, et constitue un sujet récurrent lors des entretiens techniques.
Pinia est la solution officielle de gestion d'état pour Vue 3. Vuex 5 a été annulé, et Evan You a qualifié Pinia de Vuex 5 de facto. Tous les nouveaux projets Vue 3 doivent utiliser Pinia 3.
Différences Architecturales entre Pinia et Vuex
La différence architecturale fondamentale entre Pinia et Vuex réside dans la manière dont les mutations d'état se produisent. Vuex impose un flux de données unidirectionnel strict : les composants déclenchent des actions, les actions commitent des mutations, et les mutations modifient l'état. Pinia supprime entièrement la couche de mutations, autorisant la modification directe de l'état depuis les actions ou même les composants.
Cette simplification réduit le code répétitif d'environ 40 % dans la plupart des projets. Là où Vuex nécessite quatre concepts (state, getters, mutations, actions), Pinia fonctionne avec trois (state, getters, actions).
| Fonctionnalité | Pinia 3 | Vuex 4 | |----------------|---------|--------| | Mutations | Aucune (modifications directes) | Obligatoires pour modifier l'état | | TypeScript | Inférence complète, sans augmentation | Augmentation manuelle des types | | Architecture des stores | Stores plats multiples | Store unique avec modules imbriqués | | Composition API | Support natif | Basé sur l'Options API | | Taille du bundle | ~1 Ko gzippé | ~6 Ko gzippé | | Vue Devtools | Support complet (v7) | Support complet | | SSR | Intégré | Configuration requise | | Hot Module Replacement | Intégré | Configuration manuelle |
Définir des Stores : Options API vs Syntaxe Setup
Pinia propose deux syntaxes pour définir des stores. La syntaxe Options reproduit la structure de Vuex, facilitant la migration. La syntaxe Setup exploite la Composition API de Vue 3 pour une flexibilité maximale.
import { defineStore } from 'pinia'
// Syntaxe Options Store — familière pour les développeurs Vuex
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
lastUpdated: null as Date | null,
}),
getters: {
// Les getters reçoivent le state en premier argument avec inférence de type
doubleCount: (state) => state.count * 2,
isPositive(): boolean {
// Accès aux autres getters via `this`
return this.count > 0
},
},
actions: {
increment() {
// Modification directe de l'état — pas de commit() nécessaire
this.count++
this.lastUpdated = new Date()
},
async fetchCount(id: string) {
// Les actions asynchrones fonctionnent sans configuration supplémentaire
const response = await fetch(`/api/counters/${id}`)
const data = await response.json()
this.count = data.count
},
},
})La syntaxe Options établit une correspondance claire avec les concepts Vuex. Le state remplace le state Vuex, les getters restent des getters, et les actions absorbent les actions et mutations Vuex.
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// Syntaxe Setup Store — identique aux patterns de la Composition API
export const useCounterStore = defineStore('counter', () => {
// ref() devient le state
const count = ref(0)
const lastUpdated = ref<Date | null>(null)
// computed() devient les getters
const doubleCount = computed(() => count.value * 2)
const isPositive = computed(() => count.value > 0)
// Les fonctions deviennent des actions
function increment() {
count.value++
lastUpdated.value = new Date()
}
async function fetchCount(id: string) {
const response = await fetch(`/api/counters/${id}`)
const data = await response.json()
count.value = data.count
}
// Il faut retourner tout le state, les getters et les actions
return { count, lastUpdated, doubleCount, isPositive, increment, fetchCount }
})La syntaxe Setup offre une flexibilité totale : les watchers, la réutilisation de composables et la logique conditionnelle fonctionnent naturellement à l'intérieur de la définition du store. La contrepartie est que chaque propriété réactive et chaque méthode doivent être explicitement retournées.
Intégration TypeScript : Là où Pinia Surpasse Vuex
Le support TypeScript constitue le domaine où Pinia l'emporte de manière décisive. Vuex 4 nécessite une augmentation manuelle des types via des déclarations de modules, et les modules imbriqués complexes rendent l'inférence de type fragile. Pinia infère les types automatiquement à partir de la définition du store.
// Vuex 4 — augmentation manuelle des types requise
import { Store } from 'vuex'
declare module 'vuex' {
interface State {
counter: {
count: number
lastUpdated: Date | null
}
}
}
// L'accès au state nécessite des assertions de type ou des helpers personnalisés
const count = (store.state as { counter: { count: number } }).counter.count// Pinia — inférence de type complète, zéro configuration
const counter = useCounterStore()
// TypeScript sait que counter.count est un number
// TypeScript sait que counter.doubleCount est un number
// TypeScript sait que counter.increment() retourne void
// TypeScript sait que counter.fetchCount() retourne Promise<void>
counter.increment()
console.log(counter.doubleCount)Pinia prend également en charge les stores génériques pour les patterns réutilisables, une capacité qui nécessite un boilerplate considérable dans Vuex.
Composition API et Intégration des Composables
Les stores Setup de Pinia acceptent n'importe quel composable Vue, permettant des patterns puissants comme les utilitaires partagés de VueUse ou un state conscient du routeur.
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { useRoute } from 'vue-router'
export const useSearchStore = defineStore('search', () => {
const route = useRoute()
const query = ref('')
const results = ref<SearchResult[]>([])
const isLoading = ref(false)
// Composable VueUse utilisé directement dans le store
const debouncedSearch = useDebounceFn(async (term: string) => {
if (!term.trim()) {
results.value = []
return
}
isLoading.value = true
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(term)}`)
results.value = await res.json()
} finally {
isLoading.value = false
}
}, 300)
// Le watcher synchronise le paramètre URL avec l'état du store
watch(() => route.query.q, (q) => {
if (typeof q === 'string') {
query.value = q
debouncedSearch(q)
}
})
function setQuery(term: string) {
query.value = term
debouncedSearch(term)
}
return { query, results, isLoading, setQuery }
})Ce pattern est impossible dans Vuex sans contournements. Les modules Vuex ne peuvent pas utiliser de composables, de watchers ou de hooks du routeur à l'intérieur de leur définition. La logique du store doit être répartie entre le module Vuex et des fonctions utilitaires externes.
Prêt à réussir tes entretiens Vue.js / Nuxt.js ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Communication entre Stores et Références Croisées
Vuex utilise un store racine unique avec des modules nommés. L'accès à l'état entre modules nécessite les paramètres verbeux rootState et rootGetters. Les stores Pinia sont indépendants par conception, et la communication entre stores utilise des imports directs.
import { defineStore } from 'pinia'
import { useProductStore } from './products'
import { useAuthStore } from './auth'
import { ref, computed } from 'vue'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() => {
// Accès à un autre store en appelant son composable
const productStore = useProductStore()
return items.value.reduce((sum, item) => {
const product = productStore.getById(item.productId)
return sum + (product?.price ?? 0) * item.quantity
}, 0)
})
async function checkout() {
const auth = useAuthStore()
if (!auth.isAuthenticated) {
throw new Error('Authentication required')
}
// Logique de checkout
}
return { items, total, checkout }
})Cette architecture de stores plats évite les arbres de modules profondément imbriqués qui rendent les applications Vuex volumineuses difficiles à naviguer et à refactoriser.
Migration de Vuex 4 vers Pinia 3
La migration de Vuex vers Pinia peut se faire module par module. Les deux bibliothèques peuvent coexister pendant la période de transition. La stratégie recommandée : commencer par les modules feuilles (les stores dont aucun autre module ne dépend) et progresser vers l'intérieur.
Pinia et Vuex peuvent fonctionner côte à côte dans la même application. Il suffit d'installer Pinia à côté de Vuex, de migrer un module à la fois et de supprimer Vuex uniquement après la conversion de tous les modules.
// Avant : module Vuex
// store/modules/user.ts
const userModule = {
namespaced: true,
state: () => ({
profile: null as UserProfile | null,
preferences: {} as UserPreferences,
}),
mutations: {
SET_PROFILE(state, profile: UserProfile) {
state.profile = profile
},
UPDATE_PREFERENCES(state, prefs: Partial<UserPreferences>) {
state.preferences = { ...state.preferences, ...prefs }
},
},
actions: {
async fetchProfile({ commit }) {
const profile = await api.getProfile()
commit('SET_PROFILE', profile)
},
async updatePreferences({ commit }, prefs: Partial<UserPreferences>) {
await api.updatePreferences(prefs)
commit('UPDATE_PREFERENCES', prefs)
},
},
getters: {
isLoggedIn: (state) => state.profile !== null,
displayName: (state) => state.profile?.name ?? 'Guest',
},
}// Après : store Pinia
// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { api } from '@/lib/api'
export const useUserStore = defineStore('user', () => {
const profile = ref<UserProfile | null>(null)
const preferences = ref<UserPreferences>({})
const isLoggedIn = computed(() => profile.value !== null)
const displayName = computed(() => profile.value?.name ?? 'Guest')
async function fetchProfile() {
profile.value = await api.getProfile()
}
async function updatePreferences(prefs: Partial<UserPreferences>) {
await api.updatePreferences(prefs)
Object.assign(preferences.value, prefs)
}
return { profile, preferences, isLoggedIn, displayName, fetchProfile, updatePreferences }
})Le pattern de migration est constant : les mutations se transforment en assignations directes dans les actions, les appels commit() disparaissent, et les helpers mapState/mapGetters sont remplacés par la déstructuration du composable du store.
Hydratation SSR avec Pinia et Nuxt 4
Le rendu côté serveur introduit de la complexité dans la gestion d'état. Pinia gère automatiquement la sérialisation et l'hydratation de l'état en SSR dans Nuxt 4, tandis que Vuex nécessite une gestion manuelle de window.__INITIAL_STATE__.
Les stores Setup qui utilisent des composables comme useRoute() ou useFetch() nécessitent une attention particulière en contexte SSR. Ces composables ne doivent être appelés que pendant la phase de setup, jamais à l'intérieur de callbacks asynchrones.
import { defineStore } from 'pinia'
export const useProductStore = defineStore('products', {
state: () => ({
items: [] as Product[],
selectedCategory: 'all',
}),
actions: {
async fetchProducts() {
// useFetch est spécifique à Nuxt — utiliser $fetch pour les actions de store
this.items = await $fetch<Product[]>('/api/products', {
query: { category: this.selectedCategory },
})
},
},
getters: {
getById: (state) => (id: string) =>
state.items.find((item) => item.id === id),
filteredCount: (state) =>
state.items.filter((p) => p.inStock).length,
},
})Nuxt 4 sérialise automatiquement l'état Pinia sur le serveur et l'hydrate sur le client. Aucune logique manuelle de replaceState() ou de déshydratation n'est nécessaire.
Comparaison des Performances et Impact sur le Bundle
Le poids de Pinia (~1 Ko gzippé) est environ 6 fois inférieur à celui de Vuex (~6 Ko). Dans les applications utilisant le code splitting, les stores Pinia sont tree-shakables : les stores inutilisés et leurs dépendances sont exclus des bundles de production.
Les différences de performances à l'exécution sont négligeables pour la plupart des applications. Les deux bibliothèques utilisent le système de réactivité de Vue sous le capot. Le gain de performance pratique provient de la réduction du boilerplate qui entraîne moins de re-rendus causés par des souscriptions trop larges — le modèle de stores plats de Pinia encourage des stores granulaires, ce qui signifie que les composants souscrivent à moins d'état.
Avec l'introduction du Vapor Mode dans Vue 3.6, Pinia et Vuex bénéficient tous deux du pipeline de rendu plus rapide. Cependant, l'intégration plus étroite de Pinia avec la Composition API le positionne mieux pour les optimisations du Vapor Mode.
Questions d'Entretien Fréquentes sur la Gestion d'État Vue
Les entretiens techniques testent fréquemment les connaissances en gestion d'état. Voici les questions qui reviennent le plus souvent dans les entretiens de développeurs Vue en 2026.
Q : Pourquoi la couche de mutations de Vuex a-t-elle été supprimée dans Pinia ?
Les mutations existaient dans Vuex pour permettre le débogage par voyage dans le temps (time-travel) dans les DevTools. Chaque changement d'état devait passer par une mutation synchrone pour que les DevTools puissent enregistrer des snapshots. Pinia atteint la même capacité de débogage en suivant les changements d'état au niveau réactif, rendant la couche de mutations explicite inutile. Le résultat : moins de boilerplate, même puissance de débogage.
Q : Quand faut-il préférer un store Setup à un store Options ?
Les stores Setup sont le meilleur choix lorsque le store a besoin de composables (useRoute, useDebounceFn), de watchers complexes ou de logique partagée provenant de fonctions composables externes. Les stores Options fonctionnent bien pour un state CRUD simple avec des getters basiques. En pratique, les stores Setup sont plus courants dans les bases de code en production car ils reproduisent les patterns de la Composition API des composants.
Q : Comment Pinia gère-t-elle la réactivité des objets imbriqués ?
Pinia utilise reactive() de Vue pour l'ensemble de l'objet state et ref() pour les propriétés individuelles dans les stores Setup. La réactivité profonde s'applique par défaut — les mutations d'objets imbriqués sont suivies automatiquement. Pour les cas sensibles aux performances avec de grands jeux de données, shallowRef() permet de désactiver la réactivité profonde.
Q : Expliquer les dépendances entre stores dans Pinia vs Vuex.
Dans Vuex, l'accès inter-modules utilise les paramètres rootState et rootGetters dans les actions, créant un couplage implicite. Dans Pinia, les stores s'importent directement via des appels useOtherStore(). Cela rend les dépendances explicites et permet à TypeScript de les vérifier à la compilation. Les dépendances circulaires fonctionnent dans Pinia tant qu'elles ne sont pas appelées pendant la phase de setup initiale.
Q : Que se passe-t-il avec l'état Pinia pendant l'hydratation SSR ?
Pendant le SSR, Pinia sérialise tous les états des stores actifs en JSON et intègre le résultat dans le payload HTML. Côté client, Pinia hydrate chaque store avant le montage des composants. Tout état défini pendant le rendu côté serveur est disponible immédiatement sur le client sans appels API en double. Dans Nuxt 4, ce processus est entièrement automatique.
Pour approfondir la préparation aux entretiens Vue et Nuxt, il est possible de consulter le module d'entretien sur la gestion d'état ou de revoir le module sur les composables Vue.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Conclusion
- Pinia 3 est la bibliothèque officielle de gestion d'état Vue — Vuex est en mode maintenance sans version 5 prévue
- La couche de mutations a disparu : les actions Pinia modifient l'état directement, réduisant le boilerplate d'environ 40 %
- L'inférence TypeScript fonctionne nativement dans Pinia sans aucune augmentation, contrairement aux déclarations manuelles de Vuex
- Les stores Setup s'intègrent parfaitement avec la Composition API de Vue 3, permettant la réutilisation de composables et les watchers à l'intérieur des stores
- L'architecture de stores plats remplace l'arbre de modules imbriqués de Vuex, rendant la communication inter-stores explicite et type-safe
- La migration peut se faire de manière incrémentale : Pinia et Vuex coexistent, permettant une conversion module par module
- L'hydratation SSR est automatique dans Nuxt 4 avec Pinia — aucune sérialisation manuelle nécessaire
- Pour tous les nouveaux projets Vue 3 en 2026, Pinia s'impose comme le choix évident grâce à une meilleure DX, des bundles plus légers et un support écosystème plus fort
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Nuxt 3 : SSR et génération statique, le guide complet
Maîtrisez le SSR et la génération statique avec Nuxt 3. De useFetch à generateRoutes, découvrez comment optimiser les performances de vos applications Vue.

Questions d'entretien Vue.js essentielles : 25 questions pour réussir
Préparez vos entretiens Vue.js avec ces 25 questions essentielles. De la réactivité aux composables, maîtrisez les concepts clés pour décrocher le poste.

Vue 3 Composition API : Guide complet pour maîtriser la réactivité
Découvrez Vue 3 Composition API avec ce guide pratique. Apprenez ref, reactive, computed, watch et les composables pour créer des applications Vue performantes.