Cours Vue.js · Module 3: Réactivité

Projet : Panneau de contrôle réactif du NOVA LAB

4 min de lecture
Dans cette leçon2

Projet final du module Réacteur de puissance ! Combine toutes les techniques de réactivité de Vue.js dans un seul système de surveillance. Tu vas construire un tableau de bord complet du réacteur à plasma qui suit les paramètres en temps réel, génère des alarmes et optimise l'utilisation des ressources.

Imagine le quart de nuit dans la salle de contrôle : la température du cœur monte, la pression chute, et l'ingénieur de service n'a qu'un panneau qui se rafraîchit quand quelqu'un pense à le faire. Dans ce projet, tu construis un panneau qui n'a besoin d'aucun rappel - chaque lecture, chaque alarme et chaque entrée de l'historique arrive à l'écran grâce à la façon dont tu organises les données.

Résumé du module

Dans ce module, tu as appris :

  • ref() - variables primitives réactives. Accès via .value dans les scripts, déballage automatique dans le template. Idéal pour les valeurs uniques comme la température, le statut, un compteur.
  • reactive() - objets et tableaux réactifs. Accès direct aux propriétés sans .value. Impossible de remplacer l'objet entier (uniquement les propriétés). Ne déstructure pas sans toRefs !
  • Réactivité profonde vs superficielle - shallowRef ne suit que le remplacement de .value, et shallowReactive seulement les propriétés du premier niveau. triggerRef force la mise à jour après une modification sur place. markRaw désactive complètement la réactivité. Utile pour les grosses données d'API et les bibliothèques externes.
  • toRef / toRefs - déstructuration sûre des objets réactifs tout en préservant la réactivité. toRef(obj, 'key') pour une seule propriété, toRefs(obj) pour toutes.
  • Outils de débogage - isRef(), isReactive(), unref(), toRaw() pour vérifier et déballer les valeurs réactives.
  • Modèles pratiques - panneaux avec validation, systèmes de filtrage, paniers d'achat avec getters calculés, et readonly() pour partager l'état sans droit d'écriture.

Exigences du projet

Construis un système de surveillance du réacteur qui comprend :

1. État du réacteur avec reactive()

Crée un objet réactif contenant les données du réacteur : température (Kelvin), pression (kPa), niveau d'énergie (%), statut (ACTIVE/STANDBY/CRITICAL). Ajoute des boutons pour modifier les valeurs.

Le point de départ garde la température en degrés Celsius et utilise les seuils 400 et 500 - passe-le en kelvins et aux seuils des exigences ci-dessous. Fais appeler aux boutons des fonctions qui modifient les propriétés de l'objet. Ne remplace jamais l'objet lui-même, car le template perdrait sa connexion avec lui.

2. Alarmes avec ref()

Utilise ref([]) pour stocker une liste d'alarmes système. Quand la température dépasse 4000K ou que la pression tombe sous 50kPa, ajoute automatiquement une alarme à la liste. Affiche les alertes avec la possibilité de les fermer.

Vérifie les seuils dans les fonctions mêmes qui modifient les lectures - la fonction checkAlarms du point de départ le fait déjà. Donne à chaque alarme son propre identifiant et utilise-le comme :key à la place de l'index, car les nouvelles alarmes arrivent en haut de la liste. Avant d'en ajouter une, vérifie avec la méthode some() si la même alarme est déjà affichée, pour que chaque clic n'ajoute pas un doublon.

3. Déstructuration sûre avec toRefs

Utilise toRefs pour extraire des valeurs individuelles de l'objet réacteur. Affiche-les dans des composants/sections d'interface distincts sans perdre la réactivité.

Un changement fait par un tel ref, par exemple temperature.value += 100, modifie aussi reactor.temperature - c'est le même câble, branché aux deux bouts.

4. Optimisation avec markRaw

Crée un tableau de lectures historiques et marque-le avec markRaw pour ne pas surcharger le système de réactivité. Affiche les 10 dernières lectures dans un panneau d'historique.

Un piège se cache ici : Vue n'observe pas du tout un tableau marqué avec markRaw, donc push() seul ne rafraîchira pas le panneau d'historique. Garde le tableau dans un shallowRef et appelle triggerRef après l'ajout d'une lecture, exactement comme dans la leçon sur la réactivité superficielle :

1const history = shallowRef(markRaw([]))
2
3function recordReading(reading) {
4  history.value.push(markRaw(reading))
5  triggerRef(history)
6}

Chaque lecture reste un objet ordinaire sans Proxy, et le panneau se rafraîchit une fois par entrée. Tu afficheras les dix dernières entrées avec history.slice(-10) dans le template, où le ref est déjà déballé.

Je te recommande de construire le projet par couches : d'abord les données, puis les actions, et le template en dernier, en vérifiant chaque couche dans les Vue DevTools. Dans le module 4, au Centre de calcul, tu remplaceras la vérification manuelle des seuils par des propriétés computed et des watchers.

Enrichis le point de départ ci-dessous !

Retiens : un panneau de contrôle n'est fiable que si ses données le sont - garde-les en un seul endroit, modifie-les uniquement par des actions et choisis ta réactivité en connaissance de cause.

Code de cette leçon : App.vue
1<script setup>
2import { ref, reactive, toRefs, markRaw } from 'vue'
3
4// Système de surveillance du réacteur - projet final du module 3
5const reactor = reactive({
6  name: 'Reactor Alpha',
7  temperature: 350,
8  pressure: 101,
9  energy: 85,
10  status: 'NOMINAL'
11})
12
13const { temperature, pressure, energy, status } = toRefs(reactor)
14
15const alarms = ref([])
16
17// Journaux historiques - n'ont pas besoin de réactivité
18const historicalLogs = markRaw([
19  { time: '08:00', event: 'System start' },
20  { time: '08:15', event: 'Reactor online' },
21  { time: '08:30', event: 'All systems nominal' }
22])
23
24function adjustTemperature(delta) {
25  reactor.temperature += delta
26  checkAlarms()
27}
28
29function adjustPressure(delta) {
30  reactor.pressure += delta
31  checkAlarms()
32}
33
34function checkAlarms() {
35  if (reactor.temperature > 500) {
36    alarms.value.unshift('CRITIQUE : Température dépassée !')
37    reactor.status = 'CRITICAL'
38  } else if (reactor.temperature > 400) {
39    alarms.value.unshift('AVERTISSEMENT : Température élevée')
40    reactor.status = 'WARNING'
41  } else {
42    reactor.status = 'NOMINAL'
43  }
44}
45
46// TODO : Ajoute plus de paramètres au réacteur
47// TODO : Ajoute des alarmes automatiques pour la pression
48// TODO : Enrichis le système de journaux
49</script>
50
51<template>
52  <div class="reactor-monitor">
53    <h1>Système de surveillance du réacteur</h1>
54    <div class="status-bar" :class="status.toLowerCase()">
55      Statut : {{ status }}
56    </div>
57
58    <div class="panels">
59      <div class="panel">
60        <h3>Température</h3>
61        <p class="value">{{ temperature }}°C</p>
62        <button @click="adjustTemperature(-10)">-10</button>
63        <button @click="adjustTemperature(10)">+10</button>
64      </div>
65      <div class="panel">
66        <h3>Pression</h3>
67        <p class="value">{{ pressure }} kPa</p>
68        <button @click="adjustPressure(-5)">-5</button>
69        <button @click="adjustPressure(5)">+5</button>
70      </div>
71      <div class="panel">
72        <h3>Énergie</h3>
73        <p class="value">{{ energy }}%</p>
74      </div>
75    </div>
76
77    <div class="alarms" v-if="alarms.length">
78      <h3>Alarmes</h3>
79      <p v-for="(alarm, i) in alarms" :key="i">{{ alarm }}</p>
80    </div>
81  </div>
82</template>
83
84<style scoped>
85.reactor-monitor { background: #0a0a2e; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
86h1 { color: #ff6b35; text-align: center; }
87.status-bar { text-align: center; padding: 0.5rem; border-radius: 4px; font-weight: bold; margin: 1rem 0; }
88.status-bar.nominal { background: #1a4a1a; color: #00ff88; }
89.status-bar.warning { background: #4a4a1a; color: #ffaa00; }
90.status-bar.critical { background: #4a1a1a; color: #ff4444; }
91.panels { display: flex; gap: 1rem; flex-wrap: wrap; }
92.panel { background: #1a1a4e; padding: 1rem; border-radius: 8px; flex: 1; min-width: 150px; text-align: center; }
93.value { font-size: 2rem; color: #00d4ff; margin: 0.5rem 0; }
94button { background: #333; color: white; border: none; padding: 0.5rem 1rem; margin: 0.25rem; cursor: pointer; border-radius: 4px; }
95button:hover { background: #555; }
96.alarms { margin-top: 1rem; background: #2a1a1a; padding: 1rem; border-radius: 8px; }
97.alarms h3 { color: #ff4444; }
98.alarms p { color: #ffaa00; font-size: 0.9rem; }
99</style>

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