Pinia — Gestion d'état globale
Dans ce chapitre, vous apprendrez à utiliser Pinia pour gérer des données partagées entre les composants et les pages, et à implémenter la fonctionnalité "articles favoris".
Pourquoi avons-nous besoin d’une gestion d’état globale ?
Jusqu’à présent, les données ont été transmises entre les composants via props et emit.
C’est suffisant pour des scénarios simples parent-enfant, mais cela devient fastidieux dans les situations suivantes :
- L'état des favoris doit être synchronisé entre la page d'accueil et la page de détail.
- L'état de connexion de l'utilisateur doit être partagé entre toutes les pages.
- Les données du panier doivent être transmises entre la barre de navigation, la page produit et la page de paiement.
Passer les props couche par coucheLe problème : si les données du composant A doivent être transmises au composant D, elles doivent passer par B et C comme relais, même si ces composants intermédiaires eux-mêmes n'ont pas besoin des données.
PiniaPinia est la bibliothèque de gestion d'état officiellement recommandée par Vue 3. Elle fournit un « centre de données » global où n'importe quel composant peut lire et modifier directement les données sans passage couche par couche.
Installation et enregistrement de Pinia
Si vous avez déjà sélectionné Pinia lors de la création du projet, ignorez les étapes d'installation.
Sinon :
$ npm install pinia
Enregistrez-le dans main.js :
Exemple
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
// Créer une instance Pinia et l'enregistrer
const pinia = createPinia()
app.use(pinia)
app.use(router)
app.mount('#app')
defineStore — Définition d'un Store
Le cœur de Pinia est leStoreChaque Store gère une partie indépendante de l'état.
Il se compose de trois parties :
| Partie | Objectif | Analogie |
|---|---|---|
| state | Données du Store | Données / ref du composant |
| getters | Données calculées dérivées | Computed du composant |
| actions | Méthodes de modification des données | Méthodes / fonction du composant |
Exemple
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// defineStore('storeName', () => { ... })
// Le deuxième paramètre utilise une syntaxe de fonction, cohérente avec le style de la Composition API.
export const useFavoriteStore = defineStore('favorite', () => {
// ====== state : définir l'état avec ref ======
// Utiliser un tableau pour stocker les ID des articles mis en favori
const favoriteIds = ref([])
// Restaurer la liste des favoris depuis localStorage
const saved = localStorage.getItem('blog-favorites')
if (saved) {
favoriteIds.value = JSON.parse(saved)
}
// ====== getters : définir des propriétés calculées avec computed ======
const favoriteCount = computed(() => favoriteIds.value.length)
// Déterminer si un article a été ajouté aux favoris
function isFavorite(articleId) {
return favoriteIds.value.includes(articleId)
}
// ====== actions : définir des méthodes de modification avec des fonctions régulières ======
// Basculer le statut de favori (favori → non favori, non favori → favori)
function toggleFavorite(articleId) {
const index = favoriteIds.value.indexOf(articleId)
if (index === -1) {
favoriteIds.value.push(articleId) // Ajouter aux favoris
} else {
favoriteIds.value.splice(index, 1) // Retirer des favoris
}
// Synchroniser avec localStorage (stockage persistant)
localStorage.setItem('blog-favorites', JSON.stringify(favoriteIds.value))
}
return { favoriteIds, favoriteCount, isFavorite, toggleFavorite }
})
Le deuxième paramètre de defineStore peut s'écrire de deux manières :Options Store(similaire à la syntaxe d'objet data/methods/computed de Vue 2) etSetup Store(une syntaxe de fonction utilisant la Composition API). Il est recommandé d'utiliser le Setup Store, qui est cohérent avec la
<script setup>syntaxe utilisée dans les composants, et a le coût d'apprentissage le plus faible.
Utilisation du Store dans les composants
Tout composant peutuseFavoriteStore()obtenir une instance de store, puis lire/écrire directement l'état et appeler des actions.
Ajout d'un bouton Favori dans BlogCard
Exemple
<script setup>
import { useFavoriteStore } from '../stores/useFavoriteStore.js'
const props = defineProps({
id: Number,
title: String,
summary: String,
date: String,
category: String
})
const favoriteStore = useFavoriteStore()
// Appelé lorsque le bouton favori est cliqué
function handleFavorite(e) {
e.preventDefault() // Empêche RouterLink de naviguer
favoriteStore.toggleFavorite(props.id)
}
</script>
<template>
<RouterLink :to="{ name: 'post', params: { id } }" class="card-link">
<div class="card">
<span class="tag">{{ category }}</span>
<h3>{{ title }}</h3>
<p>{{ summary }}</p>
<div class="card-footer">
<span class="date">{{ date }}</span>
<!-- Bouton favori : plein lorsqu'il est en favori, contour lorsqu'il ne l'est pas -->
<button class="fav-btn" @click="handleFavorite">
{{ favoriteStore.isFavorite(id) ? '♥' : '♡' }}
</button>
</div>
</div>
</RouterLink>
</template>
<style scoped>
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
.fav-btn {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #e74c3c;
padding: 4px 8px;
}
.fav-btn:hover {
transform: scale(1.2);
}
</style>
Ajoutez également un bouton Favori sur la page de détail
Exemple
<script setup>
import { useFavoriteStore } from '../stores/useFavoriteStore.js'
const favoriteStore = useFavoriteStore()
// ... autre code
</script>
<template>
<article v-if="article">
<div class="post-header">
<h1>{{ article.title }}</h1>
<!-- Bouton Favori sur la page de détail : état entièrement synchronisé avec la page d'accueil -->
<button class="fav-btn" @click="favoriteStore.toggleFavorite(article.id)">
{{ favoriteStore.isFavorite(article.id) ? '♥Ajouté aux favoris' : '♡ Favori' }}
</button>
</div>
<!-- ... -->
</article>
</template>
L'état des favoris sur la page d'accueil et la page de détail est entièrement synchronisé : après avoir ajouté un article aux favoris sur la page d'accueil, l'ouverture de la page de détail affiche automatiquement "Ajouté aux favoris".
C'est la valeur de la gestion d'état globale : les données sont stockées à un seul endroit, et tous les composants lisent et écrivent dans la même source. Il n'est pas nécessaire de transmettre via emit ou de faire passer les props de couche en couche.
Affichage du nombre de favoris dans NavBar
Affichez le nombre de favoris sur le côté droit de la barre de navigation ; cliquez dessus pour voir la liste des articles ajoutés aux favoris.
Exemple
<script setup>
import { useFavoriteStore } from '../stores/useFavoriteStore.js'
const favoriteStore = useFavoriteStore()
</script>
<template>
<header class="navbar">
<a href="/" class="logo">EXAMPLE Blog</a>
<nav>
<!-- Badge du nombre de favoris -->
<span class="fav-badge" v-if="favoriteStore.favoriteCount > 0">
Favoris {{ favoriteStore.favoriteCount }}
</span>
</nav>
</header>
</template>
<style scoped>
.fav-badge {
background: #e74c3c;
color: #fff;
padding: 4px 10px;
border-radius: 12px;
font-size: 13px;
}
</style>
Le compteur dans la NavBar reste automatiquement synchronisé avec la dernière liste de favoris — ajouter ou retirer un article des favoris modifie immédiatement le nombre dans la barre de navigation.
Résumé des trois capacités essentielles de Pinia
| Capacité | Code correspondant | Application dans la fonctionnalité de favoris |
|---|---|---|
| Partage global | N'importe quel composant peut utiliser useXxxStore() | NavBar, BlogCard et PostView partagent la même liste de favoris. |
| Réactivité | Lorsque l'état change, tous les consommateurs se mettent à jour automatiquement. | Après avoir ajouté aux favoris, le bouton de la page d'accueil devient rouge, le compteur de la barre de navigation augmente de 1, et la page de détail se met à jour de manière synchrone. |
| Persistance | Écrire dans localStorage dans les actions | La liste de favoris existe toujours après l'actualisation de la page. |
Résumé du chapitre
Dans ce chapitre, vous avez maîtrisé l'utilisation complète de Pinia pour la gestion d'état globale : utiliser defineStore pour définir un store (state/getters/actions), utiliser useXxxStore dans les composants pour obtenir une instance, et implémenter la persistance des données via localStorage.
La fonctionnalité « articles favoris » rend la synchronisation des données entre la page d'accueil, la page de détail et la barre de navigation très naturelle, et tous les composants restent automatiquement cohérents.
Autres extensions