Retour au cours

frontend / vuejs

Template syntax & directives de base

Leçon 21 exercice

Explication

Ce que vous allez apprendre

  • Lier dynamiquement un attribut HTML à une expression JavaScript avec v-bind (:)
  • Écouter un événement DOM avec v-on (@) et utiliser ses modificateurs courants
  • Distinguer v-if (ajoute/retire du DOM) de v-show (bascule seulement le CSS)
  • Itérer sur une liste avec v-for en fournissant systématiquement une :key stable
  • Comprendre pourquoi v-for et v-if ne doivent jamais cohabiter sur le même élément

Dans quel contexte ?

Une page affiche une fiche utilisateur avec un badge visuel différent selon son statut (admin ou membre), une section de détails qu'on peut afficher ou masquer, et une liste de fruits favoris modifiable. Chacun de ces besoins correspond exactement à une directive Vue précise : :class pour le badge conditionnel, v-if/v-show pour la section repliable, v-for pour la liste.

D'abord, lier un attribut HTML à une donnée réactive

v-bind, le plus souvent écrit avec son raccourci :, connecte un attribut HTML à une expression JavaScript évaluée dynamiquement. :src="utilisateur.avatarUrl" met à jour automatiquement l'image affichée dès que avatarUrl change, sans qu'il faille manipuler le DOM manuellement.

Appliqué à class, v-bind accepte un objet où chaque clé est une classe CSS et chaque valeur une condition : :class="{ 'badge-admin': utilisateur.admin }" n'active la classe que si utilisateur.admin est vrai.

Prérequis

Cette leçon suppose d'avoir suivi la première leçon (composant de base, ref()). Aucune autre connaissance n'est nécessaire.

Ensuite, écouter ce que fait l'utilisateur

v-on, raccourci @, écoute un événement DOM et exécute du code en réponse : @click="afficherDetails = !afficherDetails" bascule une valeur réactive au clic. Des modificateurs comme .prevent ou .enter évitent d'écrire manuellement event.preventDefault() ou de tester quelle touche a été pressée.

ModificateurEffet
.preventÉquivaut à event.preventDefault()
.stopÉquivaut à event.stopPropagation()
.enterNe déclenche l'événement que sur la touche Entrée
.onceN'exécute le gestionnaire qu'une seule fois

Il reste une distinction essentielle entre deux façons de cacher un élément

v-if retire complètement l'élément du DOM quand la condition est fausse, et le recrée entièrement quand elle redevient vraie. v-show, à l'inverse, garde toujours l'élément dans le DOM et bascule uniquement sa propriété CSS display.

Astuce

Pour un élément basculé très fréquemment (un menu qui s'ouvre/ferme au survol, par exemple), v-show est plus performant car il évite de recréer le DOM à chaque bascule. Pour un contenu affiché rarement ou conditionné à des données qui n'existent pas encore, v-if évite de garder inutilement du DOM inutilisé en mémoire.

Maintenant, itérer sur une collection sans perdre le fil

v-for génère un élément par entrée d'un tableau ou d'un objet. La :key associée n'est pas optionnelle : elle donne à Vue un identifiant stable pour chaque élément, lui permettant de réconcilier intelligemment la liste (déplacer un élément existant plutôt que le détruire et le recréer) au lieu de tout reconstruire à chaque changement.

Piège fréquent

Combiner v-for et v-if sur le MÊME élément est explicitement interdit en Vue 3, car v-for a une priorité plus élevée dans l'évaluation interne, rendant le résultat imprévisible. La solution propre : filtrer les données en amont avec une computed (vue en leçon 6), ou englober la boucle dans un <template v-for> distinct du v-if.

Maintenant que tu sais lier le DOM à des données réactives, la prochaine leçon plonge au cœur de ce système de réactivité lui-même : la différence entre ref() et reactive(), et les pièges qui les accompagnent.

Commandes & code

Template syntax & directives de base

Les directives (v-xxx) lient dynamiquement le DOM à l'état du composant.

vue
<script setup>
import { ref } from 'vue'

const utilisateur = ref({ nom: 'Alice', admin: true, avatarUrl: '/avatars/alice.png' })
const afficherDetails = ref(false)
const fruits = ref(['pomme', 'banane', 'kiwi'])
const lienSite = ref('https://vuejs.org')
</script>

<template>
  <!-- v-bind (ou raccourci :) : lie un attribut HTML à une expression JS -->
  <img :src="utilisateur.avatarUrl" :alt="`Avatar de ${utilisateur.nom}`" />
  <a :href="lienSite" target="_blank">Documentation</a>

  <!-- v-bind sur class : objet -> clé activée si valeur truthy -->
  <div :class="{ 'badge-admin': utilisateur.admin, 'badge-user': !utilisateur.admin }">
    {{ utilisateur.nom }}
  </div>

  <!-- v-bind sur style : objet camelCase -->
  <p :style="{ color: utilisateur.admin ? 'crimson' : 'gray', fontWeight: 'bold' }">
    Statut : {{ utilisateur.admin ? 'Administrateur' : 'Membre' }}
  </p>

  <!-- v-on (ou raccourci @) : écoute d'événement -->
  <button @click="afficherDetails = !afficherDetails">
    {{ afficherDetails ? 'Masquer' : 'Afficher' }} les détails
  </button>

  <!-- v-if / v-else-if / v-else : rendu conditionnel (ajoute/retire du DOM) -->
  <div v-if="afficherDetails">
    <p v-if="utilisateur.admin">Accès complet au panneau d'administration.</p>
    <p v-else>Accès limité aux fonctionnalités standard.</p>
  </div>

  <!-- v-show : bascule display:none, l'élément reste dans le DOM (mieux pour du toggle fréquent) -->
  <p v-show="afficherDetails">Section visible/masquée sans recréer le DOM.</p>

  <!-- v-for : itération, :key OBLIGATOIRE pour aider Vue à réconcilier la liste -->
  <ul>
    <li v-for="(fruit, index) in fruits" :key="fruit">
      {{ index + 1 }}. {{ fruit }}
    </li>
  </ul>

  <!-- v-for sur un objet -->
  <ul>
    <li v-for="(valeur, cle) in utilisateur" :key="cle">{{ cle }} : {{ valeur }}</li>
  </ul>

  <!-- v-for avec plage numérique -->
  <span v-for="n in 5" :key="n">{{ n }} </span>

  <!-- Modificateurs d'événements : évitent le boilerplate preventDefault/stopPropagation -->
  <form @submit.prevent="() => console.log('formulaire soumis sans reload')">
    <input @keyup.enter="() => console.log('Entrée pressée')" />
    <button type="submit">Envoyer</button>
  </form>
</template>
html
<!-- v-for + v-if sur le MÊME élément est interdit en Vue 3 (priorité de v-for) -->
<!-- Mauvais : -->
<li v-for="item in items" v-if="item.actif" :key="item.id">{{ item.nom }}</li>

<!-- Bon : filtrer en amont avec une computed, ou englober dans un <template> -->
<template v-for="item in items" :key="item.id">
  <li v-if="item.actif">{{ item.nom }}</li>
</template>

Résumé

  • :attr (v-bind) lie un attribut, @event (v-on) écoute un événement.
  • v-if retire/ajoute du DOM, v-show bascule seulement le CSS display.
  • v-for nécessite systématiquement une :key stable et unique (jamais l'index si la liste est réordonnée).
  • Ne jamais combiner v-for et v-if sur le même élément : filtrer les données en amont.

Exercices pratiques

1 disponible
1

Mission : corriger une liste de tâches instable

Objectif : Réparer une liste `v-for` bogguée et son filtre conditionnel mal placé, puis justifier chaque correction.

Contexte

Une liste de tâches affiche des bugs visuels bizarres : quand une tâche est supprimée au milieu de la liste, ce sont parfois les mauvaises cases à cocher qui changent d'état. Le code utilise <li v-for="(tache, index) in taches" :key="index"> et affiche uniquement les tâches actives avec v-if="tache.active" posé sur ce même <li>. Tu dois diagnostiquer les deux problèmes distincts et proposer un template propre.

Résoudre l’exercice →