3. Les bases de Vue 3
Comprendre la réactivité, les directives et le cycle de vie des composants Vue 3
Dans ce chapitre, on construit notre Show Watchlist — une application de gestion de séries à voir et déjà vus. Chaque concept est illustré avec un extrait du composant final présenté en fin de chapitre.
Réactivité
La réactivité est au cœur de Vue 3 : quand vos données changent, l'interface se met à jour automatiquement.
ref()
ref() crée une valeur réactive. Elle s'utilise pour les types simples (nombre, chaîne, booléen) mais aussi pour les tableaux et objets.
<template>
<p>Séries dans la watchlist : {{ shows.length }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue'
interface Show {
id: number
title: string
genre: string
year: number
seen: boolean
}
const search = ref<string>('')
const shows = ref<Show[]>([
{ id: 1, title: 'Inception', genre: 'Sci-Fi', year: 2010, seen: true },
{ id: 2, title: 'The Dark Knight', genre: 'Action', year: 2008, seen: false },
{ id: 3, title: 'Interstellar', genre: 'Sci-Fi', year: 2014, seen: false },
])
</script>
Remarque : Dans le
<template>, Vue déroule automatiquement le.value. Dans le<script>, il faut toujours passer parshows.valuepour lire ou modifier le tableau.
Les fonctions
En Vue, vous pouvez déclarer des fonctions classiques pour calculer ou transformer des données :
<template>
<p>{{ getSeenShows().length }} série(s) vue(s) sur {{ shows.length }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// ... shows déclaré avec ref()
function getSeenShows() {
return shows.value.filter((m) => m.seen)
}
</script>
Ça fonctionne — mais getSeenShows() est appelée à chaque re-rendu du composant, même si shows n'a pas changé. Pour le constater, ajoutez un console.log et un bouton qui force un re-rendu sans toucher à shows :
<template>
<p>{{ getSeenShows().length }} série(s) vue(s) sur {{ shows.length }}</p>
<button @click="count++">Re-rendre ({{ count }})</button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const shows = ref([
{ title: 'Inception', seen: true },
{ title: 'Dune', seen: false },
])
function getSeenShows() {
console.log('getSeenShows appelée !') // s'affiche à chaque clic
return shows.value.filter((m) => m.seen)
}
</script>
Ouvrez la console : getSeenShows appelée ! apparaît à chaque clic, même si shows n'a pas bougé. Remplacez ensuite la fonction par computed — le log ne s'affiche plus lors des clics, seulement quand shows change réellement.
C'est là qu'intervient
computed().
computed()
computed() crée une valeur dérivée, recalculée automatiquement uniquement quand ses dépendances changent.
<template>
<p>{{ seenShows.length }} série(s) vue(s) sur {{ shows.length }}</p>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
// ... shows déclaré avec ref()
const seenShows = computed(() =>
shows.value.filter((m) => m.seen)
)
</script>
watch()
watch() permet d'exécuter du code en réaction au changement d'une valeur réactive. Utile pour déclencher des effets
secondaires (appel API, log, etc.).
<script setup lang="ts">
import { ref, watch } from 'vue'
const search = ref<string>('')
watch(search, (newVal) => {
console.log('Recherche :', newVal)
})
</script>
Pour surveiller plusieurs sources à la fois :
watch([search, count], ([newSearch, newCount]) => {
console.log(newSearch, newCount)
})
Cycle de vie des composants
Vue appelle des hooks à des moments précis de la vie d'un composant. Le plus utilisé est onMounted, appelé une
fois que le composant est inséré dans le DOM — idéal pour déclencher un appel API initial.
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const shows = ref([])
onMounted(() => {
console.log('Application prête')
// Ici on pourrait charger les séries depuis une API
})
</script>
Les principaux hooks :
| Hook | Moment d'appel |
|---|---|
onMounted | Après l'insertion du composant dans le DOM |
onUpdated | Après une mise à jour du DOM |
onUnmounted | Avant la destruction du composant |
onBeforeMount | Juste avant le montage |
Documentation : La liste complète des hooks est disponible sur fr.vuejs.org/guide/essentials/lifecycle.
Directives Vue
Vue fournit des directives pour manipuler le DOM de manière déclarative directement dans le template.
v-if / v-else / v-else-if
Affiche ou masque un élément selon une condition. L'élément est créé ou détruit dans le DOM.
<template>
<p v-if="seenShows.length === 0">Aucune série vue pour l'instant.</p>
<ul>
<li v-for="show in shows" :key="show.id">
<span v-if="show.seen">✓ Vu</span>
<span v-else>À voir</span>
</li>
</ul>
</template>
v-for
Génère une liste d'éléments dynamiquement. Toujours utiliser :key avec un identifiant unique.
<template>
<ul>
<li v-for="show in shows" :key="show.id">
{{ show.title }} ({{ show.year }}) — {{ show.genre }}
</li>
</ul>
</template>
v-bind
Lie dynamiquement une valeur à un attribut HTML. Le raccourci est :.
<template>
<!-- Ajoute la classe CSS "seen" si la série a été vue -->
<li v-for="show in shows" :key="show.id" :class="{ seen: show.seen }">
{{ show.title }}
</li>
</template>
v-model
v-model synchronise automatiquement un champ de formulaire avec une variable réactive dans les deux sens :
- Quand la variable change dans le script → le champ se met à jour
- Quand l'utilisateur tape dans le champ → la variable se met à jour
C'est ce qu'on appelle une liaison bidirectionnelle.
Sans v-model, il faudrait gérer ces deux directions manuellement :
<template>
<!-- Sans v-model : gestion manuelle -->
<input :value="search" @input="search = $event.target.value"/>
<!-- Avec v-model : équivalent, mais en une directive -->
<input v-model="search" placeholder="Rechercher une série..."/>
</template>
v-on
Écoute des événements DOM. Le raccourci est @.
<template>
<!-- Forme longue -->
<button v-on:click="show.seen = !show.seen">Basculer</button>
<!-- Raccourci @ -->
<button @click="show.seen = !show.seen">Basculer</button>
</template>
Différence v-bind vs v-model
v-bind(:) : liaison unidirectionnelle (données → DOM). Pour les attributs commesrc,href,class.v-model: liaison bidirectionnelle (données ↔ DOM). Pour les inputs, selects, textareas.
Exemple complet
Voici le composant Show Watchlist qui combine tous les concepts du chapitre :
<template>
<div>
<input v-model="search" placeholder="Rechercher une série..."/>
<p v-if="seenShows.length === 0">Aucune série vue pour l'instant.</p>
<ul>
<li v-for="show in shows" :key="show.id" :class="{ seen: show.seen }">
{{ show.title }} ({{ show.year }}) — {{ show.genre }}
<span v-if="show.seen">✓ Vu</span>
<span v-else>À voir</span>
<button @click="show.seen = !show.seen">Basculer</button>
</li>
</ul>
<p>{{ seenShows.length }} série(s) vue(s) sur {{ shows.length }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
interface Show {
id: number
title: string
genre: string
year: number
seen: boolean
}
const search = ref<string>('')
const shows = ref<Show[]>([
{ id: 1, title: 'Inception', genre: 'Sci-Fi', year: 2010, seen: true },
{ id: 2, title: 'The Dark Knight', genre: 'Action', year: 2008, seen: false },
{ id: 3, title: 'Interstellar', genre: 'Sci-Fi', year: 2014, seen: false },
{ id: 4, title: 'Parasite', genre: 'Thriller', year: 2019, seen: true },
{ id: 5, title: 'Dune', genre: 'Sci-Fi', year: 2021, seen: false },
{ id: 6, title: 'Everything Everywhere All at Once', genre: 'Action', year: 2022, seen: true },
])
const seenShows = computed(() =>
shows.value.filter((m) => m.seen)
)
watch(search, (newVal) => {
console.log('Recherche :', newVal)
})
onMounted(() => {
console.log('Application prête')
})
</script>