Vue 3 Composition API : Guide complet pour maîtriser la réactivité
Découvrez Vue 3 Composition API avec ce guide pratique. Apprenez ref, reactive, computed, watch et les composables pour créer des applications Vue performantes.

La Composition API de Vue 3 représente une évolution majeure dans la façon de structurer les composants Vue. Cette approche permet d'organiser le code par fonctionnalité plutôt que par option, facilitant la réutilisation et la maintenance des applications complexes.
Ce guide suppose une connaissance de base de Vue.js. Les exemples utilisent la syntaxe <script setup> introduite dans Vue 3.2, qui est désormais la méthode recommandée.
Comprendre la réactivité avec ref et reactive
La réactivité est au cœur de Vue 3. Deux primitives principales permettent de créer des données réactives : ref pour les valeurs primitives et reactive pour les objets complexes.
La fonction ref crée une référence réactive qui encapsule une valeur. Pour accéder ou modifier cette valeur dans le script, il faut utiliser la propriété .value. Dans le template, Vue décompresse automatiquement cette propriété.
<script setup>
import { ref } from 'vue'
// Création d'une ref avec valeur initiale 0
const count = ref(0)
// Fonction qui incrémente le compteur
// Note: .value est nécessaire dans le script
const increment = () => {
count.value++
}
// Fonction de réinitialisation
const reset = () => {
count.value = 0
}
</script>
<template>
<!-- Dans le template, pas besoin de .value -->
<div class="counter">
<p>Compteur : {{ count }}</p>
<button @click="increment">+1</button>
<button @click="reset">Réinitialiser</button>
</div>
</template>Pour les objets avec plusieurs propriétés liées, reactive offre une syntaxe plus naturelle sans avoir besoin de .value.
<script setup>
import { reactive } from 'vue'
// reactive pour les objets complexes
// Toutes les propriétés sont automatiquement réactives
const user = reactive({
name: 'Marie Dupont',
email: 'marie@example.com',
preferences: {
theme: 'dark',
notifications: true
}
})
// Modification directe des propriétés (pas de .value)
const updateTheme = (newTheme) => {
user.preferences.theme = newTheme
}
// Attention: ne pas réassigner l'objet entier
// user = { ... } casserait la réactivité
</script>
<template>
<div>
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<p>Thème : {{ user.preferences.theme }}</p>
</div>
</template>La règle générale : utiliser ref pour les valeurs primitives (string, number, boolean) et reactive pour les objets structurés.
Propriétés calculées avec computed
Les propriétés calculées permettent de dériver des valeurs à partir de l'état réactif. Elles sont mises en cache et ne se recalculent que lorsque leurs dépendances changent, ce qui les rend très performantes.
<script setup>
import { ref, computed } from 'vue'
// Liste de produits
const products = ref([
{ id: 1, name: 'Laptop', price: 999, inStock: true },
{ id: 2, name: 'Souris', price: 29, inStock: true },
{ id: 3, name: 'Clavier', price: 79, inStock: false },
{ id: 4, name: 'Écran', price: 299, inStock: true }
])
// Filtre actif
const showOnlyInStock = ref(false)
// computed: filtrage automatique selon le toggle
// Se recalcule uniquement si products ou showOnlyInStock change
const filteredProducts = computed(() => {
if (showOnlyInStock.value) {
return products.value.filter(p => p.inStock)
}
return products.value
})
// computed: calcul du total avec mise en cache
const totalValue = computed(() => {
return filteredProducts.value.reduce((sum, p) => sum + p.price, 0)
})
// computed: nombre d'articles affichés
const productCount = computed(() => filteredProducts.value.length)
</script>
<template>
<div>
<label>
<input type="checkbox" v-model="showOnlyInStock" />
Afficher uniquement les produits en stock
</label>
<p>{{ productCount }} produits - Total : {{ totalValue }}€</p>
<ul>
<li v-for="product in filteredProducts" :key="product.id">
{{ product.name }} - {{ product.price }}€
</li>
</ul>
</div>
</template>Les computed peuvent également être en écriture avec un getter et un setter, utile pour les transformations bidirectionnelles.
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Jean')
const lastName = ref('Martin')
// computed avec getter ET setter
const fullName = computed({
// Lecture: combine prénom et nom
get() {
return `${firstName.value} ${lastName.value}`
},
// Écriture: sépare la chaîne en prénom/nom
set(newValue) {
const parts = newValue.split(' ')
firstName.value = parts[0] || ''
lastName.value = parts.slice(1).join(' ') || ''
}
})
</script>
<template>
<!-- La modification de fullName met à jour firstName et lastName -->
<input v-model="fullName" placeholder="Nom complet" />
<p>Prénom: {{ firstName }}</p>
<p>Nom: {{ lastName }}</p>
</template>Observateurs avec watch et watchEffect
Les watchers permettent d'exécuter des effets secondaires en réponse aux changements de données. Vue 3 propose deux approches : watch pour un contrôle précis et watchEffect pour le tracking automatique.
watch offre un contrôle précis sur les dépendances et fournit l'ancienne et la nouvelle valeur. watchEffect est plus simple quand toutes les dépendances réactives utilisées doivent déclencher l'effet.
<script setup>
import { ref, watch, watchEffect } from 'vue'
const searchQuery = ref('')
const searchResults = ref([])
const isLoading = ref(false)
// watch: observe une source spécifique
// Fournit oldValue et newValue, permet le debounce
watch(searchQuery, async (newQuery, oldQuery) => {
console.log(`Recherche changée de "${oldQuery}" vers "${newQuery}"`)
// Ne pas rechercher si moins de 3 caractères
if (newQuery.length < 3) {
searchResults.value = []
return
}
isLoading.value = true
try {
// Appel API simulé
const response = await fetch(`/api/search?q=${newQuery}`)
searchResults.value = await response.json()
} finally {
isLoading.value = false
}
}, {
// Options du watcher
debounce: 300, // Attendre 300ms après la dernière frappe
immediate: false // Ne pas exécuter immédiatement
})
// watchEffect: tracking automatique des dépendances
// S'exécute immédiatement et à chaque changement
watchEffect(() => {
// Toutes les refs utilisées ici sont automatiquement trackées
console.log(`État actuel: ${searchResults.value.length} résultats`)
console.log(`Loading: ${isLoading.value}`)
})
</script>
<template>
<div>
<input v-model="searchQuery" placeholder="Rechercher..." />
<p v-if="isLoading">Chargement...</p>
<ul v-else>
<li v-for="result in searchResults" :key="result.id">
{{ result.title }}
</li>
</ul>
</div>
</template>Pour observer des objets imbriqués ou des tableaux, l'option deep est nécessaire avec watch.
<script setup>
import { reactive, watch } from 'vue'
const settings = reactive({
display: {
theme: 'light',
fontSize: 14
},
notifications: {
email: true,
push: false
}
})
// deep: true pour observer les changements imbriqués
watch(
() => settings.display,
(newDisplay) => {
console.log('Paramètres display modifiés:', newDisplay)
// Sauvegarder en localStorage par exemple
localStorage.setItem('display', JSON.stringify(newDisplay))
},
{ deep: true }
)
</script>Prêt à réussir tes entretiens Vue.js / Nuxt.js ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Créer des composables réutilisables
Les composables sont des fonctions qui encapsulent de la logique réactive réutilisable. Cette approche est l'un des atouts majeurs de la Composition API pour partager du code entre composants.
import { ref, watchEffect, toValue } from 'vue'
// Composable pour les requêtes HTTP
// Gère automatiquement le loading, les erreurs et le refetch
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
const isLoading = ref(false)
// Fonction de fetch réutilisable
async function fetchData() {
isLoading.value = true
error.value = null
try {
// toValue() permet d'accepter une ref ou une valeur
const response = await fetch(toValue(url))
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
data.value = await response.json()
} catch (e) {
error.value = e.message
} finally {
isLoading.value = false
}
}
// watchEffect pour refetch automatique si url est une ref
watchEffect(() => {
fetchData()
})
// Exposer l'état et les méthodes
return {
data,
error,
isLoading,
refetch: fetchData
}
}Ce composable s'utilise ensuite dans n'importe quel composant de manière déclarative.
<script setup>
import { useFetch } from '@/composables/useFetch'
// Utilisation simple du composable
const { data: users, isLoading, error, refetch } = useFetch('/api/users')
</script>
<template>
<div>
<button @click="refetch" :disabled="isLoading">
Actualiser
</button>
<p v-if="isLoading">Chargement des utilisateurs...</p>
<p v-else-if="error" class="error">Erreur : {{ error }}</p>
<ul v-else-if="users">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</div>
</template>Voici un autre exemple de composable pour gérer l'état d'un formulaire avec validation.
import { reactive, computed } from 'vue'
// Composable de gestion de formulaire
export function useForm(initialValues, validationRules) {
// État du formulaire
const form = reactive({
values: { ...initialValues },
errors: {},
touched: {}
})
// Valider un champ spécifique
const validateField = (field) => {
const rules = validationRules[field]
if (!rules) return true
for (const rule of rules) {
const result = rule(form.values[field])
if (result !== true) {
form.errors[field] = result
return false
}
}
form.errors[field] = null
return true
}
// Valider tout le formulaire
const validate = () => {
let isValid = true
for (const field in validationRules) {
if (!validateField(field)) {
isValid = false
}
}
return isValid
}
// computed: formulaire valide si aucune erreur
const isValid = computed(() => {
return Object.values(form.errors).every(e => !e)
})
// Marquer un champ comme touché (pour afficher les erreurs)
const touch = (field) => {
form.touched[field] = true
validateField(field)
}
// Réinitialiser le formulaire
const reset = () => {
form.values = { ...initialValues }
form.errors = {}
form.touched = {}
}
return {
form,
isValid,
validate,
validateField,
touch,
reset
}
}Gestion du cycle de vie
La Composition API propose des hooks de cycle de vie sous forme de fonctions à appeler dans le setup. Ces hooks permettent d'exécuter du code à des moments précis de la vie du composant.
Toujours nettoyer les effets secondaires (timers, event listeners, subscriptions) dans onUnmounted pour éviter les fuites mémoire.
<script setup>
import {
ref,
onMounted,
onUnmounted,
onBeforeUpdate,
onUpdated
} from 'vue'
const windowWidth = ref(window.innerWidth)
const updateCount = ref(0)
// Handler pour le resize
const handleResize = () => {
windowWidth.value = window.innerWidth
}
// onMounted: composant inséré dans le DOM
// Idéal pour les appels API initiaux et les event listeners
onMounted(() => {
console.log('Composant monté')
window.addEventListener('resize', handleResize)
// Exemple: initialiser une librairie tierce
// chart = new Chart(chartRef.value, config)
})
// onUnmounted: composant retiré du DOM
// CRITIQUE: nettoyer tous les effets secondaires
onUnmounted(() => {
console.log('Composant démonté')
window.removeEventListener('resize', handleResize)
// Nettoyer les subscriptions, timers, etc.
// chart?.destroy()
})
// onBeforeUpdate: avant la mise à jour du DOM
onBeforeUpdate(() => {
console.log('Avant mise à jour')
})
// onUpdated: après la mise à jour du DOM
onUpdated(() => {
updateCount.value++
console.log(`DOM mis à jour (${updateCount.value} fois)`)
})
</script>
<template>
<div>
<p>Largeur de fenêtre : {{ windowWidth }}px</p>
<p>Mises à jour du DOM : {{ updateCount }}</p>
</div>
</template>Refs de template et accès au DOM
Les refs de template permettent d'accéder directement aux éléments DOM ou aux instances de composants enfants. Cela reste utile pour les cas où la manipulation directe est nécessaire.
<script setup>
import { ref, onMounted } from 'vue'
// ref pour l'élément DOM
// Même nom que l'attribut ref dans le template
const inputRef = ref(null)
// Fonction pour focus l'input
const focusInput = () => {
// Accès à l'élément DOM natif
inputRef.value?.focus()
}
// Focus automatique au montage
onMounted(() => {
focusInput()
})
// Exposer la méthode au parent si nécessaire
defineExpose({
focus: focusInput
})
</script>
<template>
<div>
<!-- L'attribut ref lie l'élément à inputRef -->
<input ref="inputRef" type="text" placeholder="Focus automatique" />
<button @click="focusInput">Redonner le focus</button>
</div>
</template>Communication parent-enfant avec props et emits
La Composition API modernise la déclaration des props et events avec defineProps et defineEmits, offrant une meilleure intégration TypeScript.
<script setup>
// defineProps: déclare les props attendues
// Avec valeurs par défaut via withDefaults
const props = withDefaults(defineProps<{
title: string
count?: number
items?: string[]
}>(), {
count: 0,
items: () => []
})
// defineEmits: déclare les événements émis
// Typage précis des payloads
const emit = defineEmits<{
(e: 'update', value: number): void
(e: 'submit', data: { title: string; items: string[] }): void
}>()
// Fonction qui émet un événement
const handleSubmit = () => {
emit('submit', {
title: props.title,
items: props.items
})
}
const increment = () => {
emit('update', props.count + 1)
}
</script>
<template>
<div class="card">
<h3>{{ title }}</h3>
<p>Count: {{ count }}</p>
<button @click="increment">Incrémenter</button>
<button @click="handleSubmit">Soumettre</button>
</div>
</template><script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const currentCount = ref(0)
const items = ref(['Item 1', 'Item 2'])
// Handler pour l'événement update
const onUpdate = (newValue) => {
currentCount.value = newValue
}
// Handler pour l'événement submit
const onSubmit = (data) => {
console.log('Données soumises:', data)
}
</script>
<template>
<ChildComponent
title="Mon composant"
:count="currentCount"
:items="items"
@update="onUpdate"
@submit="onSubmit"
/>
</template>Prêt à réussir tes entretiens Vue.js / Nuxt.js ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Provide et Inject pour l'injection de dépendances
Pour partager des données entre composants distants sans prop drilling, Vue 3 propose provide et inject.
<script setup>
import { provide, ref, readonly } from 'vue'
// État global du thème
const theme = ref('light')
// Fonction pour changer le thème
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
// provide: rend disponible aux descendants
// readonly empêche la modification directe par les enfants
provide('theme', readonly(theme))
provide('toggleTheme', toggleTheme)
</script>
<template>
<div :class="theme">
<slot />
</div>
</template><script setup>
import { inject } from 'vue'
// inject: récupère la valeur fournie par un ancêtre
// Valeur par défaut si non trouvé
const theme = inject('theme', 'light')
const toggleTheme = inject('toggleTheme', () => {})
</script>
<template>
<div>
<p>Thème actuel : {{ theme }}</p>
<button @click="toggleTheme">
Basculer vers {{ theme === 'light' ? 'sombre' : 'clair' }}
</button>
</div>
</template>Conclusion
La Composition API de Vue 3 offre une approche puissante et flexible pour organiser le code des applications Vue. Les concepts clés à retenir :
- ✅ ref pour les valeurs primitives, reactive pour les objets complexes
- ✅ computed pour les valeurs dérivées avec mise en cache automatique
- ✅ watch et watchEffect pour les effets secondaires réactifs
- ✅ Composables pour extraire et réutiliser la logique entre composants
- ✅ Hooks de cycle de vie fonctionnels (
onMounted,onUnmounted, etc.) - ✅ provide/inject pour l'injection de dépendances sans prop drilling
Cette approche facilite la création d'applications maintenables et testables, tout en offrant une excellente intégration TypeScript. La prochaine étape consiste à explorer les patterns avancés comme les composables asynchrones et l'intégration avec Pinia pour la gestion d'état globale.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Questions d'entretien Vue.js essentielles : 25 questions pour réussir
Préparez vos entretiens Vue.js avec ces 25 questions essentielles. De la réactivité aux composables, maîtrisez les concepts clés pour décrocher le poste.

Nuxt 3 : SSR et génération statique, le guide complet
Maîtrisez le SSR et la génération statique avec Nuxt 3. De useFetch à generateRoutes, découvrez comment optimiser les performances de vos applications Vue.