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

// Chemin du fichier : src/main.js
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 :

PartieObjectifAnalogie
stateDonnées du StoreDonnées / ref du composant
gettersDonnées calculées dérivéesComputed du composant
actionsMéthodes de modification des donnéesMéthodes / fonction du composant

Exemple

// Chemin du fichier : src/stores/useFavoriteStore.js
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

<!-- Chemin du fichier : src/components/BlogCard.vue -->
<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) ? '&#x2665;' : '♡' }}
        </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

<!-- Chemin du fichier : src/views/PostView.vue (code partiel) -->
<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) ? '&#x2665;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

<!-- Chemin du fichier : src/components/NavBar.vue -->
<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 correspondantApplication dans la fonctionnalité de favoris
Partage globalN'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 actionsLa 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