Cours Vue.js · Module 11: Pinia

Les bases de Pinia

3 min de lecture
Dans cette leçon5

Bienvenue dans la base de données centrale du NOVA LAB ! Année 2087 - c'est ici que nous stockons toutes les données critiques de la mission martienne. Tout comme l'ordinateur principal de la base conserve les informations sur les systèmes, l'équipage et les ressources, Pinia stocke l'état global d'une application Vue.

Qu'est-ce que Pinia ?

Pinia est la bibliothèque officielle de gestion d'état pour Vue 3. C'est le successeur de Vuex et il propose une syntaxe bien plus simple. Considère-le comme l'ordinateur central du NOVA LAB - un point de contrôle unique d'où tous les modules de la station récupèrent leurs données.

Principaux avantages de Pinia :

  • Syntaxe plus simple que Vuex (pas de mutations !)
  • Support complet de TypeScript
  • Excellente intégration avec les Vue DevTools
  • Architecture modulaire - plusieurs stores indépendants

Installation et configuration

Pour démarrer Pinia, il te faut deux étapes - installer le paquet et le connecter à l'application :

1npm install pinia
1// main.js
2import { createApp } from 'vue'
3import { createPinia } from 'pinia'
4import App from './App.vue'
5
6const pinia = createPinia()
7const app = createApp(App)
8
9app.use(pinia)
10app.mount('#app')

La fonction createPinia() crée une instance de Pinia, et app.use(pinia) l'enregistre en tant que plugin Vue. À partir de ce moment, tous les stores sont disponibles dans toute l'application.

Définir un store - defineStore

Un store se crée avec defineStore. Il prend deux arguments : un nom de store unique et une fonction setup (style Composition API) :

1// stores/mission.js
2import { defineStore } from 'pinia'
3import { ref, computed } from 'vue'
4
5export const useMissionStore = defineStore('mission', () => {
6  // State - données réactives
7  const missionName = ref('Mars Exploration Alpha')
8  const status = ref('active')
9  const crewCount = ref(6)
10
11  // Getters - valeurs calculées
12  const statusLabel = computed(() =>
13    status.value === 'active' ? 'ACTIVE' : 'INACTIVE'
14  )
15
16  // Actions - fonctions qui modifient l'état
17  function updateStatus(newStatus) {
18    status.value = newStatus
19  }
20
21  // On retourne tout ce qui doit être accessible
22  return {
23    missionName,
24    status,
25    crewCount,
26    statusLabel,
27    updateStatus
28  }
29})

Convention de nommage : useXxxStore - préfixe use et suffixe Store.

Utiliser le store dans un composant

1<script setup>
2import { useMissionStore } from '@/stores/mission'
3
4const missionStore = useMissionStore()
5
6// Accès à l'état
7console.log(missionStore.missionName)
8
9// Appeler une action
10missionStore.updateStatus('completed')
11</script>
12
13<template>
14  <div>
15    <h1>{{ missionStore.missionName }}</h1>
16    <p>Status: {{ missionStore.statusLabel }}</p>
17    <button @click="missionStore.updateStatus('paused')">
18      Pause Mission
19    </button>
20  </div>
21</template>

storeToRefs - déstructuration réactive

La déstructuration classique fait perdre la réactivité. Utilise storeToRefs pour l'état et les getters :

1<script setup>
2import { useMissionStore } from '@/stores/mission'
3import { storeToRefs } from 'pinia'
4
5const store = useMissionStore()
6
7// State et getters - storeToRefs (préserve la réactivité)
8const { missionName, status, statusLabel } = storeToRefs(store)
9
10// Actions - déstructuration classique (ce sont des fonctions, pas des refs)
11const { updateStatus } = store
12</script>
Code de cette leçon : App.vue
1<script setup>
2import { ref, computed } from 'vue'
3
4// Simulation d'un store Pinia (style Composition API)
5// Dans une vraie application : import { defineStore } from 'pinia'
6
7// State
8const missionName = ref('Mars Exploration Alpha')
9const status = ref('active')
10const crewCount = ref(6)
11const fuelLevel = ref(87)
12
13// Getters
14const statusLabel = computed(() =>
15  status.value === 'active' ? 'ACTIVE' : 'INACTIVE'
16)
17
18const fuelStatus = computed(() => {
19  if (fuelLevel.value > 70) return 'Optimal'
20  if (fuelLevel.value > 30) return 'Moderate'
21  return 'Critical'
22})
23
24// Actions
25function toggleStatus() {
26  status.value = status.value === 'active' ? 'paused' : 'active'
27}
28
29function consumeFuel(amount) {
30  fuelLevel.value = Math.max(0, fuelLevel.value - amount)
31}
32
33function refuel() {
34  fuelLevel.value = 100
35}
36</script>
37
38<template>
39  <div class="nova-lab">
40    <h1>NOVA LAB - Pinia Basics</h1>
41
42    <div class="panel">
43      <h2>Mission Store</h2>
44      <div class="stat">
45        <span class="label">Mission:</span>
46        <span>{{ missionName }}</span>
47      </div>
48      <div class="stat">
49        <span class="label">Status:</span>
50        <span :class="status">{{ statusLabel }}</span>
51      </div>
52      <div class="stat">
53        <span class="label">Crew:</span>
54        <span>{{ crewCount }} members</span>
55      </div>
56      <div class="stat">
57        <span class="label">Fuel:</span>
58        <div class="fuel-bar">
59          <div class="fuel-fill" :style="{ width: fuelLevel + '%' }" :class="fuelStatus.toLowerCase()"></div>
60        </div>
61        <span>{{ fuelLevel }}% ({{ fuelStatus }})</span>
62      </div>
63
64      <div class="actions">
65        <button @click="toggleStatus" class="btn">
66          {{ status === 'active' ? 'Pause' : 'Resume' }}
67        </button>
68        <button @click="consumeFuel(10)" class="btn btn-warn">
69          Consume Fuel (-10)
70        </button>
71        <button @click="refuel" class="btn btn-success">
72          Refuel
73        </button>
74      </div>
75    </div>
76  </div>
77</template>
78
79<style scoped>
80.nova-lab {
81  background: #0a0e27;
82  color: #00ff88;
83  min-height: 100vh;
84  padding: 20px;
85  font-family: monospace;
86}
87h1 { color: #00ff88; margin-bottom: 20px; text-align: center; }
88.panel {
89  background: rgba(0, 180, 216, 0.1);
90  border: 2px solid #00b4d8;
91  padding: 20px;
92  max-width: 500px;
93  margin: 0 auto;
94}
95h2 { color: #00b4d8; margin-bottom: 15px; }
96.stat {
97  display: flex;
98  align-items: center;
99  gap: 10px;
100  margin: 10px 0;
101  flex-wrap: wrap;
102}
103.label { color: #00b4d8; min-width: 80px; }
104.active { color: #00ff88; font-weight: bold; }
105.paused { color: #ff9900; font-weight: bold; }
106.fuel-bar {
107  width: 150px;
108  height: 16px;
109  background: #1a1e3f;
110  border: 1px solid #00b4d8;
111}
112.fuel-fill {
113  height: 100%;
114  transition: width 0.3s;
115}
116.fuel-fill.optimal { background: #00ff88; }
117.fuel-fill.moderate { background: #ff9900; }
118.fuel-fill.critical { background: #ff4444; }
119.actions {
120  display: flex;
121  gap: 8px;
122  margin-top: 15px;
123  flex-wrap: wrap;
124}
125.btn {
126  background: #00b4d8;
127  color: #0a0e27;
128  border: none;
129  padding: 8px 16px;
130  cursor: pointer;
131  font-family: monospace;
132  font-weight: bold;
133}
134.btn:hover { background: #00ff88; }
135.btn-warn { background: #ff9900; }
136.btn-warn:hover { background: #ffbb33; }
137.btn-success { background: #00ff88; }
138.btn-success:hover { background: #33ffaa; }
139</style>

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Qu'est-ce que Pinia dans l'écosystème Vue 3 ?

  2. 2. Comment crée-t-on une instance de Pinia dans main.js ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Crée ton premier store Pinia avec state, getters et actions

  • Clique dans l'ordre

    Ordonne les éléments pour appeler correctement defineStore

  • Éditeur de code

    Utilise un store Pinia dans un composant Vue