Skip to main content

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 par shows.value pour 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 :

HookMoment d'appel
onMountedAprès l'insertion du composant dans le DOM
onUpdatedAprès une mise à jour du DOM
onUnmountedAvant la destruction du composant
onBeforeMountJuste 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 comme src, 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>