Cours Vue.js · Module 7: Props et événements
Motifs de communication avancés
Dans cette leçon4
Maintenant que tu connais tous les outils de communication de Vue, voyons comment les combiner en motifs avancés - comme un système de communication complet du NOVA LAB.
Le problème n'est plus de savoir comment envoyer un signal, mais par quel canal. Une même information peut passer par les props, un événement, v-model, provide ou un magasin d'état global. Un mauvais choix ne se voit qu'avec le temps : des données poussées à travers cinq étages, un enfant qui modifie en douce l'état d'un autre, ou un parent qui fouille à l'intérieur d'un enfant. Voici la carte des canaux et les motifs qui mettent de l'ordre dans le trafic de la station.
Quand utiliser quel motif ?
Le tableau classe les canaux de la portée la plus courte à la plus large. Pinia, à la dernière ligne, est la bibliothèque officielle de Vue pour gérer l'état global - tu la découvriras à la Mission Database.
| Motif | Quand l'utiliser |
|---|---|
| Props | Transmettre des données parent → enfant (1 niveau) |
| Events | Communication enfant → parent (1 niveau) |
| v-model | Liaison bidirectionnelle (formulaires, inputs) |
| Provide/Inject | Communication en profondeur (2+ niveaux) |
| Pinia | État global de l'application |
Les props et les événements ne relient que des voisins, v-model est leur paire réunie en une seule écriture, provide atteint n'importe quel descendant du sous-arbre, et Pinia est accessible depuis n'importe quel endroit de l'application.
Motif : Readonly Provide + Actions
Un motif sûr - les descendants ne peuvent pas modifier l'état directement. Le fournisseur expose l'état via readonly, et à côté une fonction qui est la seule autorisée à modifier cet état :
1<!-- Fournisseur -->
2<script setup>
3import { provide, ref, readonly } from 'vue'
4
5const state = ref({ theme: 'dark', lang: 'fr' })
6
7provide('state', readonly(state))
8provide('updateState', (key, value) => {
9 state.value[key] = value
10})
11</script>
12
13<!-- Consommateur -->
14<script setup>
15import { inject } from 'vue'
16
17const state = inject('state') // readonly
18const updateState = inject('updateState') // action
19
20updateState('theme', 'light') // OK - via une action
21// state.value.theme = 'light' // readonly : avertissement dans la console, changement ignoré
22</script>L'affectation de la dernière ligne ne lève pas d'exception : Vue affiche dans la console un avertissement indiquant que la cible est en lecture seule, et ignore la modification. Chaque modification passe donc par updateState, c'est-à-dire par un seul endroit où il est facile d'ajouter une journalisation ou une vérification des données. L'état a toujours un seul propriétaire - le fournisseur.
Motif : defineExpose
Parfois, un parent doit donner un ordre à un enfant, par exemple « réinitialise le formulaire ». Les composants avec <script setup> sont fermés par défaut : un parent qui y accède via une ref de template ne voit aucune variable ni aucune fonction. La macro defineExpose en ouvre certaines - c'est ainsi qu'un composant expose ses méthodes au parent via une ref :
1<!-- Child.vue -->
2<script setup>
3import { ref } from 'vue'
4
5const count = ref(0)
6const reset = () => { count.value = 0 }
7const validate = () => count.value > 0
8
9defineExpose({ reset, validate })
10</script>
11
12<!-- Parent.vue -->
13<script setup>
14import { ref } from 'vue'
15
16const childRef = ref(null)
17
18const handleReset = () => {
19 childRef.value.reset()
20 const isValid = childRef.value.validate()
21}
22</script>
23
24<template>
25 <Child ref="childRef" />
26 <button @click="handleReset">Reset Child</button>
27</template>L'attribut ref="childRef" relie le composant à la ref du même nom dans le script du parent. La valeur n'apparaît qu'après le montage - avant, childRef.value vaut null - c'est pourquoi on appelle les méthodes en réaction à un clic. Depuis Vue 3.5, la documentation recommande d'écrire useTemplateRef('childRef') au lieu de ref(null), et cela fonctionne de la même façon. Le parent ne voit que reset et validate, tandis que count reste privé.
Flux de données complet
Dans une application complète, tous les canaux fonctionnent en même temps. Voici à quoi peut ressembler l'arbre de composants d'un panneau où chaque connexion utilise un motif différent :
1App (provide: état global)
2 └── Layout
3 ├── Header (inject: theme, user)
4 │ └── Avatar (props: user.avatar)
5 │ └── emit('click') → Header
6 └── Content
7 └── Page (props depuis le router)
8 └── Form (v-model: données)
9 └── emit('submit') → Page → APILa boucle est toujours la même. Le parent garde l'état dans un ref ou un reactive et le transmet en props, et l'enfant affiche les données en lecture seule. L'enfant peut avoir son propre état de travail, par exemple un brouillon saisi dans un formulaire, mais cela reste son affaire locale. Quand il veut modifier les données du parent, il émet un événement ; le parent met à jour l'état dans le gestionnaire, et Vue refait le rendu de tout ce qui utilise cet état. Un composant intermédiaire comme Layout peut en même temps injecter ce que diffuse App et fournir ses propres données à son sous-arbre. Les props de Page, elles, peuvent être fournies par le router - tu le verras au Centre de Navigation.
Mon conseil : choisis le canal le plus proche qui suffit. Considère defineExpose comme un port de maintenance - pour des méthodes comme reset() ou focus(), pas comme voie principale des données. Dans l'éditeur ci-dessous, AlertPanel modifie le niveau d'alerte uniquement via des actions injectées. Dans le projet de cette station, tous ces motifs fonctionneront ensemble dans un seul système.
Retiens : la meilleure liaison de la station est le chemin le plus court qui achemine le signal - avec toujours un seul propriétaire des données.
Code de cette leçon : App.vue
1<script setup>
2import { ref, provide, readonly } from 'vue'
3import Dashboard from './Dashboard.vue'
4
5// Motifs de communication avancés
6const stationStatus = ref('operational')
7const alertLevel = ref(0)
8
9// Provide avec le motif readonly + actions
10provide('station', readonly(stationStatus))
11provide('alertLevel', readonly(alertLevel))
12provide('raiseAlert', () => {
13 alertLevel.value = Math.min(alertLevel.value + 1, 5)
14})
15provide('clearAlert', () => {
16 alertLevel.value = 0
17})
18</script>
19
20<template>
21 <div class="nova-lab">
22 <h1>Motifs avancés</h1>
23 <p>Station: {{ stationStatus }} | Alert: {{ alertLevel }}/5</p>
24 <Dashboard />
25 </div>
26</template>
27
28<style scoped>
29.nova-lab {
30 background: #0a0e27;
31 color: #00ff88;
32 min-height: 100vh;
33 padding: 20px;
34 font-family: monospace;
35}
36h1 { color: #00ff88; margin-bottom: 15px; }
37</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. Comment désactiver l'héritage automatique des attributs ?
2. Comment accéder aux attrs dans <script setup> ?
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 motifs de communication du plus proche au plus éloigné :
- Tri horizontal
Ordonne la syntaxe des v-model multiples :
- Clique dans l'ordre
Ordonne le provide typé avec InjectionKey :
- Tri vertical
Ordonne le flux de données à sens unique dans Vue :
- Tri horizontal
Ordonne la syntaxe de defineEmits avec validation de la charge utile :
- Clique dans l'ordre
Ordonne le cycle de mise à jour de v-model sur un composant :
- Tri horizontal
Ordonne le code pour un provide global dans main.ts :
- Clique dans l'ordre
Ordonne le motif readonly provide :
- Tri vertical
Ordonne le flux de données complet dans une application Vue :
- Tri vertical
Ordonne l'arbre de communication du haut (app) vers le bas (feuille) :
- Tri horizontal
Ordonne la syntaxe de defineExpose :