Cours Vue.js · Module 11: Pinia
Stores en Composition API (syntaxe setup)
Dans cette leçon5
Au NOVA LAB, chaque système possède son propre protocole de configuration. Pinia propose deux styles pour définir les stores - la Composition API (fonction setup) et l'Options API. La Composition API est recommandée car elle est plus proche de la façon dont on écrit les composants Vue 3.
Composition API vs Options API
1// Composition API (RECOMMANDÉE)
2export const useCounterStore = defineStore('counter', () => {
3 const count = ref(0)
4 const doubleCount = computed(() => count.value * 2)
5 function increment() { count.value++ }
6
7 return { count, doubleCount, increment }
8})
9
10// Options API
11export const useCounterStore = defineStore('counter', {
12 state: () => ({ count: 0 }),
13 getters: {
14 doubleCount: (state) => state.count * 2
15 },
16 actions: {
17 increment() { this.count++ }
18 }
19})Quand choisir quel style ?
| Aspect | Composition API | Options API |
|---|---|---|
| Flexibilité | Totale | Limitée |
| TypeScript | Typage naturel | Nécessite des déclarations |
$reset() | À définir manuellement | Fonctionne automatiquement |
| Composables | Faciles à utiliser | Plus difficile |
| Syntaxe | Comme <script setup> | Comme l'Options API de Vue |
Utiliser des composables dans un store
La Composition API permet d'utiliser facilement des composables à l'intérieur d'un store :
1import { defineStore } from 'pinia'
2import { ref, computed, watch } from 'vue'
3import { useLocalStorage } from '@vueuse/core'
4
5export const useSettingsStore = defineStore('settings', () => {
6 // Un composable à l'intérieur d'un store !
7 const theme = useLocalStorage('theme', 'dark')
8 const language = useLocalStorage('language', 'en')
9
10 const isDark = computed(() => theme.value === 'dark')
11
12 function toggleTheme() {
13 theme.value = isDark.value ? 'light' : 'dark'
14 }
15
16 return { theme, language, isDark, toggleTheme }
17})Watchers dans un store
Tu peux utiliser watch et watchEffect à l'intérieur d'un store :
1export const useAlertStore = defineStore('alerts', () => {
2 const temperature = ref(22)
3 const alerts = ref([])
4
5 // Watcher réagissant aux changements
6 watch(temperature, (newTemp, oldTemp) => {
7 if (newTemp > 50) {
8 alerts.value.push({
9 type: 'critical',
10 message: `Temperature critical: ${newTemp}°C`,
11 timestamp: Date.now()
12 })
13 }
14 })
15
16 function setTemperature(value) {
17 temperature.value = value
18 }
19
20 return { temperature, alerts, setTemperature }
21})Typage avec TypeScript
1interface CrewMember {
2 id: number
3 name: string
4 role: 'commander' | 'engineer' | 'scientist'
5 active: boolean
6}
7
8export const useCrewStore = defineStore('crew', () => {
9 const members = ref<CrewMember[]>([])
10 const selected = ref<CrewMember | null>(null)
11
12 const activeMembers = computed<CrewMember[]>(() =>
13 members.value.filter(m => m.active)
14 )
15
16 function addMember(member: Omit<CrewMember, 'id'>): CrewMember {
17 const newMember: CrewMember = {
18 ...member,
19 id: Date.now()
20 }
21 members.value.push(newMember)
22 return newMember
23 }
24
25 return { members, selected, activeMembers, addMember }
26})Code de cette leçon : App.vue
1<script setup>
2import { ref, computed, watch } from 'vue'
3
4// Store Composition API avec watchers et patterns TypeScript
5const temperature = ref(22)
6const alerts = ref([])
7const settings = ref({
8 theme: 'dark',
9 language: 'fr',
10 unit: 'celsius'
11})
12
13// Watcher à l'intérieur du store
14watch(temperature, (newTemp) => {
15 if (newTemp > 45) {
16 alerts.value.push({
17 id: Date.now(),
18 type: 'critical',
19 message: `Temperature critical: ${newTemp} C`,
20 time: new Date().toLocaleTimeString()
21 })
22 } else if (newTemp > 35) {
23 alerts.value.push({
24 id: Date.now(),
25 type: 'warning',
26 message: `Temperature high: ${newTemp} C`,
27 time: new Date().toLocaleTimeString()
28 })
29 }
30})
31
32// Getters
33const isDark = computed(() => settings.value.theme === 'dark')
34const tempDisplay = computed(() => {
35 if (settings.value.unit === 'fahrenheit') {
36 return ((temperature.value * 9/5) + 32).toFixed(1) + ' F'
37 }
38 return temperature.value + ' C'
39})
40
41const criticalAlerts = computed(() =>
42 alerts.value.filter(a => a.type === 'critical')
43)
44
45// Actions
46function setTemperature(value) {
47 temperature.value = Math.max(-100, Math.min(100, value))
48}
49
50function toggleTheme() {
51 settings.value.theme = isDark.value ? 'light' : 'dark'
52}
53
54function toggleUnit() {
55 settings.value.unit = settings.value.unit === 'celsius' ? 'fahrenheit' : 'celsius'
56}
57
58function dismissAlert(id) {
59 alerts.value = alerts.value.filter(a => a.id !== id)
60}
61
62function clearAlerts() {
63 alerts.value = []
64}
65</script>
66
67<template>
68 <div class="nova-lab" :class="settings.theme">
69 <h1>NOVA LAB - Composition Store</h1>
70
71 <div class="temp-control">
72 <h2>Temperature Control</h2>
73 <div class="temp-display" :class="{ critical: temperature > 45, warning: temperature > 35 }">
74 {{ tempDisplay }}
75 </div>
76 <div class="slider-row">
77 <button @click="setTemperature(temperature - 5)" class="btn">-5</button>
78 <input type="range" v-model.number="temperature" min="-50" max="80" class="slider" />
79 <button @click="setTemperature(temperature + 5)" class="btn">+5</button>
80 </div>
81 </div>
82
83 <div class="settings-panel">
84 <h2>Settings (composable pattern)</h2>
85 <button @click="toggleTheme" class="btn">
86 Theme: {{ settings.theme }}
87 </button>
88 <button @click="toggleUnit" class="btn">
89 Unit: {{ settings.unit }}
90 </button>
91 </div>
92
93 <div v-if="alerts.length" class="alerts-panel">
94 <div class="alerts-header">
95 <h2>Alerts ({{ alerts.length }})</h2>
96 <button @click="clearAlerts" class="btn btn-sm">Clear All</button>
97 </div>
98 <div v-for="alert in alerts" :key="alert.id"
99 class="alert-item" :class="alert.type">
100 <span>{{ alert.time }} - {{ alert.message }}</span>
101 <button @click="dismissAlert(alert.id)" class="dismiss">x</button>
102 </div>
103 </div>
104 </div>
105</template>
106
107<style scoped>
108.nova-lab {
109 min-height: 100vh;
110 padding: 20px;
111 font-family: monospace;
112 transition: all 0.3s;
113}
114.nova-lab.dark { background: #0a0e27; color: #00ff88; }
115.nova-lab.light { background: #f0f4f8; color: #1a1e3f; }
116h1 { margin-bottom: 20px; text-align: center; }
117h2 { color: #00b4d8; margin-bottom: 10px; }
118.temp-control, .settings-panel, .alerts-panel {
119 max-width: 500px;
120 margin: 0 auto 15px;
121 padding: 15px;
122 border: 2px solid #00b4d8;
123 background: rgba(0, 180, 216, 0.1);
124}
125.temp-display {
126 text-align: center;
127 font-size: 2em;
128 font-weight: bold;
129 margin: 10px 0;
130}
131.temp-display.warning { color: #ff9900; }
132.temp-display.critical { color: #ff4444; }
133.slider-row { display: flex; align-items: center; gap: 10px; }
134.slider { flex: 1; }
135.btn {
136 background: #00b4d8;
137 color: #0a0e27;
138 border: none;
139 padding: 6px 14px;
140 cursor: pointer;
141 font-family: monospace;
142 margin: 2px;
143}
144.btn:hover { background: #00ff88; }
145.btn-sm { padding: 4px 10px; font-size: 0.85em; }
146.alerts-header { display: flex; justify-content: space-between; align-items: center; }
147.alert-item {
148 display: flex;
149 justify-content: space-between;
150 align-items: center;
151 padding: 8px;
152 margin: 5px 0;
153 border: 1px solid;
154}
155.alert-item.warning { border-color: #ff9900; color: #ff9900; }
156.alert-item.critical { border-color: #ff4444; color: #ff4444; }
157.dismiss {
158 background: none;
159 border: 1px solid currentColor;
160 color: inherit;
161 cursor: pointer;
162 padding: 2px 6px;
163 font-family: monospace;
164}
165</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. Quelle est la principale différence entre un store en Composition API et en Options API dans Pinia ?
2. Peut-on utiliser watch() à l'intérieur d'un store en Composition API ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Tri vertical
Ordonne les éléments d'un store en Composition API dans le bon ordre
- Éditeur de code
Crée un store de paramètres en utilisant la Composition API
- Tri horizontal
Ordonne les éléments pour typer un store avec TypeScript
- Éditeur de code
Crée un store complet de gestion de l'équipage