Aller au contenu

La Réactivité Vue : ref() et Données Dynamiques

Tes composants sont des cadavres statiques pour l'instant. Ils affichent des trucs, ok, mais ils ne réagissent à rien. Clique sur un bouton ? Rien. Survole un élément ? Nada. C'est triste. 💀

On va leur donner vie avec ref(), le cœur battant de Vue.

Objectifs du chapitre

À la fin de ce chapitre, tu seras capable de :

  • ✅ Comprendre ce qu'est la réactivité dans Vue
  • ✅ Créer des données réactives avec ref()
  • ✅ Modifier ces données en réponse à des actions utilisateur
  • ✅ Maîtriser la règle .value (script) vs sans .value (template)

C'est quoi la réactivité ?

En JavaScript classique, quand tu changes une variable, il ne se passe rien à l'écran. Le navigateur ne sait pas qu'il doit mettre à jour l'affichage.

// JavaScript classique
let compteur = 0
compteur++
// L'écran affiche toujours 0... 😢

Avec Vue et ref(), c'est différent. Vue surveille la donnée. Dès qu'elle change, Vue met à jour l'affichage automatiquement.

// Vue avec ref()
const compteur = ref(0)
compteur.value++
// L'écran affiche 1 instantanément ! ✨

C'est ça la réactivité : Vue réagit aux changements de données et synchronise l'affichage tout seul.

Créer une ref()

Syntaxe de base

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

// Créer des refs avec différents types de valeurs
const compteur = ref(0)           // Number
const message = ref('Hello')      // String
const isVisible = ref(true)       // Boolean
const tags = ref(['Vue', 'CSS'])  // Array
</script>

Deux étapes obligatoires :

  1. Importer ref depuis 'vue'
  2. Appeler ref() avec ta valeur initiale
Type Exemple Résultat
Number ref(0) Compteur, quantité, index
String ref('Hello') Texte, message, nom
Boolean ref(true) État on/off, visible/caché
Array ref([]) Liste d'éléments
Object ref({}) Données structurées

La règle d'or : .value

C'est LA source d'erreur numéro 1 avec ref(). Grave ça dans ton cerveau :

Contexte Syntaxe Pourquoi
Dans <script> Avec .value Tu accèdes à la vraie valeur
Dans <template> Sans .value Vue déballe automatiquement

Exemple concret

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

const compteur = ref(0)

function incrementer() {
  // ✅ Dans le script : .value obligatoire
  compteur.value++
  
  // ❌ FAUX : ça ne marchera pas
  // compteur++
}

function afficherValeur() {
  // ✅ Pour lire aussi, .value
  console.log(compteur.value)
}
</script>

<template>
  <!-- ✅ Dans le template : PAS de .value -->
  <p>Compteur : {{ compteur }}</p>
  
  <!-- ❌ FAUX : ça afficherait l'objet ref, pas la valeur -->
  <!-- <p>{{ compteur.value }}</p> -->
  
  <button @click="incrementer">+1</button>
</template>

Erreurs fréquentes

❌ Erreur ✅ Correction
compteur++ dans script compteur.value++
{ { compteur.value }} dans template { { compteur }}
if (isVisible) dans script if (isVisible.value)
Oublier l'import import { ref } from 'vue'

Modifier une ref avec des événements

Pour que quelque chose se passe quand l'utilisateur interagit, tu as besoin de :

  1. Un événement (@click, @mouseover, etc.)
  2. Une fonction qui modifie la ref
  3. Vue s'occupe du reste !

Les événements de base

Événement Déclencheur Exemple
@click Clic souris Bouton, lien
@mouseover Survol Tooltip, preview
@mouseout Quitte l'élément Fermer tooltip
@keyup Touche relâchée Recherche live
@keyup.enter Touche Entrée Valider formulaire

Exemple : Toggle visibility

Un pattern ultra-courant : afficher/cacher un élément.

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

const isMenuOpen = ref(false)

function toggleMenu() {
  isMenuOpen.value = !isMenuOpen.value
}
</script>

<template>
  <div>
    <button @click="toggleMenu" class="bg-blue-500 text-white px-4 py-2 rounded">
      {{ isMenuOpen ? 'Fermer' : 'Ouvrir' }} le menu
    </button>
    
    <nav v-if="isMenuOpen" class="mt-4 p-4 bg-gray-100 rounded">
      <a href="#" class="block py-2">Accueil</a>
      <a href="#" class="block py-2">Projets</a>
      <a href="#" class="block py-2">Contact</a>
    </nav>
  </div>
</template>

Décortiquons :

  1. isMenuOpen commence à false (menu fermé)
  2. Au clic, toggleMenu() inverse la valeur (falsetrue)
  3. Vue voit le changement et re-render
  4. v-if="isMenuOpen" affiche maintenant le menu
  5. Le texte du bouton change aussi grâce au ternaire

Exemple : Compteur avec +/-

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

const count = ref(0)

function increment() {
  count.value++
}

function decrement() {
  count.value--
}

function reset() {
  count.value = 0
}
</script>

<template>
  <div class="flex items-center gap-4">
    <button @click="decrement" class="bg-red-500 text-white w-10 h-10 rounded-full text-xl">
      -
    </button>
    
    <span class="text-4xl font-bold w-16 text-center">{{ count }}</span>
    
    <button @click="increment" class="bg-green-500 text-white w-10 h-10 rounded-full text-xl">
      +
    </button>
    
    <button @click="reset" class="bg-gray-400 text-white px-4 py-2 rounded ml-4">
      Reset
    </button>
  </div>
</template>

Affichage conditionnel : v-if

Maintenant que tu sais créer des refs booléennes (ref(true) / ref(false)), tu peux afficher ou cacher des éléments selon leur valeur.

v-if : Afficher si vrai

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

const isLoggedIn = ref(false)
</script>

<template>
  <div v-if="isLoggedIn">
    Bienvenue ! Tu es connecté.
  </div>
</template>

Comportement : Si isLoggedIn est false, l'élément n'existe pas dans le DOM. Il est complètement supprimé.

v-else : L'alternative

<template>
  <div v-if="isLoggedIn">
    Bienvenue ! Tu es connecté.
  </div>
  <div v-else>
    Connecte-toi pour continuer.
  </div>
</template>

⚠️ Important : v-else doit être immédiatement après un v-if. Pas d'élément entre les deux !

v-else-if : Plusieurs conditions

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

const status = ref('loading') // 'loading', 'success', 'error'
</script>

<template>
  <div v-if="status === 'loading'" class="text-blue-500">
    ⏳ Chargement...
  </div>
  <div v-else-if="status === 'success'" class="text-green-500">
    ✅ Succès !
  </div>
  <div v-else-if="status === 'error'" class="text-red-500">
    ❌ Erreur !
  </div>
  <div v-else class="text-gray-500">
    🤷 État inconnu
  </div>
</template>

v-if vs v-show : Quelle différence ?

Directive Comportement Utilisation
v-if Supprime l'élément du DOM Changements rares
v-show Cache avec display: none Changements fréquents
<template>
  <!-- v-if : l'élément est SUPPRIMÉ si false -->
  <div v-if="isVisible">Je suis dans le DOM ou pas</div>
  
  <!-- v-show : l'élément est CACHÉ mais reste dans le DOM -->
  <div v-show="isVisible">Je suis toujours là, juste invisible</div>
</template>

Règle simple :

  • Menu qui s'ouvre/ferme souvent → v-show
  • Contenu qui change rarement (login/logout) → v-if
  • Pas sûr ? → v-if (c'est le défaut)

Exemple complet : Alert dismissible

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

const showAlert = ref(true)

function dismissAlert() {
  showAlert.value = false
}
</script>

<template>
  <div 
    v-if="showAlert" 
    class="bg-blue-100 text-blue-800 p-4 rounded-lg flex justify-between items-center"
  >
    <span>ℹ️ Ceci est une information importante !</span>
    <button @click="dismissAlert" class="text-blue-600 hover:text-blue-800">
      ✕
    </button>
  </div>
</template>

Exercices pratiques

Exercice 1 : Toggle Show/Hide ⭐

Objectif : Maîtriser le pattern toggle de base

Ta mission : Créer un composant Spoiler.vue qui masque/révèle du contenu.

Comportement :

  • Par défaut, le contenu est caché
  • Un bouton "Voir le spoiler" / "Masquer" toggle l'affichage
  • Le texte du bouton change selon l'état

Props à implémenter :

Prop Type Description
title String Titre affiché (ex: "Spoiler saison 3")
content String Le contenu caché

Exemple d'utilisation :

<Spoiler 
  title="Fin du film" 
  content="Le majordome était le coupable depuis le début." 
/>

Exercice 2 : Bouton Like ⭐⭐

Objectif : Combiner toggle, compteur et classes dynamiques

Ta mission : Créer un composant LikeButton.vue avec :

  • Un cœur cliquable (🤍 gris / ❤️ rouge)
  • Un compteur de likes
  • Au clic : toggle like + mise à jour compteur

Props à implémenter :

Prop Type Description
initialCount Number Nombre de likes initial (défaut: 0)

Comportement :

  • Clic 1 : cœur rouge, compteur +1
  • Clic 2 : cœur gris, compteur -1
  • Style différent si liké (bordure rouge, fond rose clair)

Exemple d'utilisation :

<LikeButton :initialCount="42" />

Exercice 3 : Navigation Responsive ⭐⭐

Objectif : Créer un menu hamburger animé

Ta mission : Créer un composant NavBar.vue avec :

  • Un logo à gauche
  • Des liens de navigation (desktop : visibles, mobile : cachés)
  • Un bouton hamburger (☰) visible uniquement sur mobile
  • Au clic : le menu s'ouvre/ferme avec une animation

Props à implémenter :

Prop Type Description
logo String Texte du logo
links Array Liste des liens [{ label: 'Accueil', href: '/' }, ...]

Comportement :

  • Desktop (>768px) : liens en ligne, pas de hamburger
  • Mobile : hamburger visible, menu caché par défaut
  • Clic hamburger : menu slide de haut en bas
  • Icône toggle : ☰ (fermé) → ✕ (ouvert)

Exemple d'utilisation :

<NavBar 
  logo="MonSite" 
  :links="[
    { label: 'Accueil', href: '/' },
    { label: 'Projets', href: '/projets' },
    { label: 'Contact', href: '/contact' }
  ]" 
/>

Exercice 4 : Notation 5 étoiles ⭐⭐⭐

Objectif : Gérer le hover et le clic sur une liste

Ta mission : Créer un composant StarRating.vue avec :

  • 5 étoiles cliquables (☆ vide / ★ pleine)
  • Au survol : preview de la note
  • Au clic : la note est enregistrée
  • Affichage de la note actuelle

Props à implémenter :

Prop Type Description
initialRating Number Note initiale (0-5, défaut: 0)

Comportement :

  • Hover sur étoile 3 : étoiles 1, 2, 3 se remplissent (preview)
  • Mouse out : revient à la note enregistrée
  • Clic sur étoile 3 : note = 3 (enregistré)
  • Les étoiles remplies sont jaunes/dorées

Exemple d'utilisation :

<StarRating :initialRating="3" />

Exercice BONUS : Card Projet Complète ⭐⭐⭐

Objectif : Combiner tous les concepts dans un composant réaliste

Ta mission : Reprendre ton ProjectCard.vue du chapitre précédent et y ajouter :

  • Le composant LikeButton intégré
  • Le composant StarRating pour noter le projet
  • Un bouton "Voir détails" qui toggle une description longue (comme Spoiler)

Exemple d'utilisation :

<ProjectCard
  title="Café Bohème"
  description="Site vitrine pour un restaurant"
  image="/images/cafe.jpg"
  :tags="['Vue', 'Tailwind']"
  :initialLikes="42"
  :initialRating="4"
/>

La suite...

Tu sais maintenant créer des données réactives et les modifier. Mais pour l'instant, c'est toi (le code) qui décide quand ça change.

Dans le prochain chapitre, on verra v-model : l'utilisateur tape dans un input, et la donnée se met à jour automatiquement. C'est la magie du two-way binding, et c'est ce qui rend les formulaires Vue si agréables à coder. (ง'̀-'́)ง