1. setup() — Point d'entrée de la Composition API
La fonction setup() est le cœur de la Composition API.
Elle s'exécute avant que le composant soit monté, remplace data, methods, computed et watch, et doit retourner un objet contenant tout ce qu'on veut rendre accessible dans le template.
const { createApp, ref, computed, watch } = Vue;
createApp({
setup() {
// 1. Déclarer l'état réactif avec ref() ou reactive()
const compteur = ref(0);
const message = ref('Bonjour !');
// 2. Déclarer des fonctions (équivalent de methods)
function incrementer() {
compteur.value++;
}
// 3. TOUT ce qui est utilisé dans le template
// doit être retourné
return {
compteur,
message,
incrementer
};
}
}).mount('#app');
setup() vs Options API — Comparaison directe
// ── Options API ──────────────────────────────────────
createApp({
data() { return { n: 0, nom: 'Alice' }; },
methods: { doubler() { this.n *= 2; } },
computed: { nDouble() { return this.n * 2; } },
watch: { n(val) { console.log('n =', val); } }
}).mount('#app');
// ── Composition API ──────────────────────────────────
const { ref, computed, watch } = Vue;
createApp({
setup() {
const n = ref(0);
const nom = ref('Alice');
function doubler() { n.value *= 2; } // méthode
const nDouble = computed(() => n.value * 2); // computed
watch(n, val => console.log('n =', val)); // watch
return { n, nom, doubler, nDouble };
}
}).mount('#app');
.value en JavaScript — mais pas dans les templates ! Vue déréférence automatiquement dans le HTML.<!-- Dans le template : PAS de .value -->
<p>{{ compteur }}</p> <!-- ✅ -->
<p>{{ compteur.value }}</p> <!-- ❌ Déjà déréférencé -->
<!-- Dans setup() : .value obligatoire -->
function incrementer() {
compteur.value++; // ✅ .value obligatoire en JS
}
.value dans le code JavaScript de setup().2. ref() — Valeurs primitives réactives
ref() crée un objet réactif avec une propriété .value.
Il est utilisé pour toutes les valeurs primitives (number, string, boolean) et peut aussi envelopper des tableaux ou objets.
const { ref } = Vue;
// Primitives
const compteur = ref(0); // number
const nom = ref('Alice'); // string
const estActif = ref(true); // boolean
const notes = ref([14, 18, 12]); // tableau
const config = ref({ theme: 'dark', lang: 'fr' }); // objet
// Lire la valeur
console.log(compteur.value); // 0
console.log(nom.value); // 'Alice'
// Modifier la valeur
compteur.value++; // 1
nom.value = 'Bob'; // 'Bob'
estActif.value = !estActif.value; // false
notes.value.push(16); // [14, 18, 12, 16]
// Remplacer un tableau (aussi réactif)
notes.value = notes.value.filter(n => n >= 14); // [14, 18, 16]
ref() avec des objets complexes
const utilisateur = ref({
prenom: 'Alice',
age: 28,
competences: ['Vue', 'JavaScript']
});
// Modifier une propriété d'un objet dans une ref
utilisateur.value.age++;
utilisateur.value.prenom = 'Bob';
utilisateur.value.competences.push('TypeScript');
// Remplacer tout l'objet
utilisateur.value = { prenom: 'Charlie', age: 30, competences: [] };
Template vs JavaScript
<!-- Template : Vue déréférence automatiquement -->
<p>{{ nom }}</p> <!-- affiche 'Alice', pas [object] -->
<p>{{ compteur }}</p> <!-- affiche 0, pas { value: 0 } -->
<button @click="compteur++">+</button> <!-- ✅ sans .value -->
.value partout. Dans le template HTML = jamais de .value.3. reactive() — Objets réactifs
reactive() crée un objet réactif profond (deep reactive).
Contrairement à ref(), il n'a pas de .value — on accède directement aux propriétés.
Il est conçu pour les objets et tableaux, pas les primitives.
const { reactive } = Vue;
// Créer un objet réactif
const etat = reactive({
compteur: 0,
nom: 'Alice',
options: {
theme: 'dark',
notifications: true
},
tags: ['Vue', 'JavaScript']
});
// Accès direct (pas de .value !)
console.log(etat.compteur); // 0
console.log(etat.options.theme); // 'dark'
// Modification directe
etat.compteur++;
etat.nom = 'Bob';
etat.options.theme = 'light';
etat.tags.push('TypeScript');
Réactivité profonde
const state = reactive({
user: {
profile: {
nom: 'Alice', // ← réactif !
avatar: 'img.png' // ← réactif !
}
}
});
// Même les propriétés imbriquées sont réactives
state.user.profile.nom = 'Bob'; // Déclenche le re-render ✅
ref() vs reactive() — Choisir
- ✅ Primitives (string, number, bool)
- ✅ Tableaux simples
- ✅ Valeur qui peut être remplacée
- ✅ Retourné depuis composables
- ⚠️ Nécessite
.valueen JS
- ✅ Objets complexes et imbriqués
- ✅ Formulaires (plusieurs champs)
- ✅ État de composant groupé
- ✅ Pas de
.value - ⚠️ Ne pas déstructurer directement
// ❌ La déstructuration casse la réactivité de reactive()
const { nom, age } = reactive({ nom: 'Alice', age: 28 });
// nom et age ne sont plus réactifs !
// ✅ Solution 1 : accès via l'objet
const etat = reactive({ nom: 'Alice', age: 28 });
etat.nom; // réactif ✅
// ✅ Solution 2 : toRefs() (voir section 6)
const { nom, age } = toRefs(etat); // refs réactives ✅
reactive() entier : etat = { nouveauObj } casse la réactivité. Modifie les propriétés une par une.4. computed() — Propriétés calculées mémoïsées
computed() crée une valeur dérivée qui est mémoïsée : elle ne se recalcule que quand ses dépendances réactives changent.
Elle retourne un objet ref en lecture seule (ou lecture/écriture avec getter+setter).
const { ref, computed } = Vue;
const items = ref([
{ nom: 'Alice', age: 28, actif: true },
{ nom: 'Bob', age: 17, actif: false },
{ nom: 'Clara', age: 32, actif: true }
]);
const recherche = ref('');
// computed simple (lecture seule)
const itemsFiltres = computed(() => {
return items.value
.filter(i => i.actif)
.filter(i => i.nom.toLowerCase().includes(recherche.value.toLowerCase()));
});
const total = computed(() => items.value.length);
const totalActifs = computed(() => items.value.filter(i => i.actif).length);
// Accès comme une ref
console.log(itemsFiltres.value); // tableau filtré
console.log(total.value); // 3
computed avec getter ET setter
const prenom = ref('Alice');
const nom = ref('Dupont');
// Computed en lecture/écriture
const nomComplet = computed({
// get : calcule la valeur
get() {
return `${prenom.value} ${nom.value}`;
},
// set : décompose la valeur quand on assigne
set(valeur) {
const parties = valeur.split(' ');
prenom.value = parties[0] ?? '';
nom.value = parties[1] ?? '';
}
});
console.log(nomComplet.value); // 'Alice Dupont'
nomComplet.value = 'Bob Martin'; // modifie prenom et nom
console.log(prenom.value); // 'Bob'
console.log(nom.value); // 'Martin'
Mémoïsation vs méthode — La différence clé
const items = ref([/* 1000 items */]);
// ✅ computed — calculé UNE SEULE FOIS si items ne change pas
const total = computed(() => {
console.log('Calcul !'); // s'exécute seulement si items change
return items.value.reduce((s, i) => s + i.prix, 0);
});
// Accédé 1000 fois dans le template → calculé 1 seule fois
// total.value; total.value; total.value; ... → 1 seul "Calcul !"
// ❌ méthode — recalculée à chaque accès
function calculerTotal() {
console.log('Calcul !'); // s'exécute À CHAQUE appel
return items.value.reduce((s, i) => s + i.prix, 0);
}
// calculerTotal() × 1000 → 1000 "Calcul !"
computed() pour tout calcul dérivé de l'état. Utilise les méthodes pour les actions avec effets de bord.5. watch() et watchEffect() — Réagir aux changements
watch() observe des sources réactives spécifiques et déclenche une callback quand elles changent.
watchEffect() découvre automatiquement ses dépendances en s'exécutant.
watch() — Observation ciblée
const { ref, watch } = Vue;
const compteur = ref(0);
const nom = ref('Alice');
// Observer une ref
watch(compteur, (nouvelleVal, ancienneVal) => {
console.log(`Compteur : ${ancienneVal} → ${nouvelleVal}`);
});
// Observer plusieurs sources (tableau)
watch([compteur, nom], ([newCount, newNom], [oldCount, oldNom]) => {
console.log('Changement détecté');
});
// Observer une propriété d'un reactive (getter function)
const etat = reactive({ n: 0, texte: '' });
watch(() => etat.n, (val) => {
console.log('etat.n changé :', val);
});
// Options de watch
watch(compteur, (val) => {
console.log('Immédiat :', val);
}, {
immediate: true, // s'exécute immédiatement au montage
deep: true, // observe les changements profonds d'un objet
once: true // s'exécute une seule fois (Vue 3.4+)
});
watchEffect() — Tracking automatique
const { ref, watchEffect } = Vue;
const id = ref(1);
const titre = ref('');
// watchEffect découvre ses dépendances automatiquement
// S'exécute immédiatement (pas besoin de immediate: true)
const stop = watchEffect(async () => {
// Lit id.value → devient une dépendance automatique
const data = await fetch(`/api/posts/${id.value}`);
titre.value = (await data.json()).titre;
// Quand id.value change → cette fonction se ré-exécute
});
// Arrêter le watcher manuellement
stop(); // plus aucune observation
watch vs watchEffect
| Critère | watch() | watchEffect() |
|---|---|---|
| Dépendances | Déclarées explicitement | Détectées automatiquement |
| Exécution initiale | Non (sauf immediate) |
Oui, immédiate |
| Ancienne valeur | Disponible | Non disponible |
| Usage typique | Comparaison, logs, validations | Fetch, synchronisation |
watchEffect est idéal pour les effets de bord simples qui dépendent de plusieurs réfs. watch est meilleur quand tu as besoin de l'ancienne valeur ou d'un contrôle précis.6. toRefs() — Déstructurer un reactive sans perdre la réactivité
toRefs() convertit chaque propriété d'un objet reactive() en une ref() individuelle.
Cela permet de déstructurer l'objet sans perdre la liaison réactive.
Le problème de la déstructuration
const { reactive, toRefs } = Vue;
const etat = reactive({ nom: 'Alice', age: 28, score: 100 });
// ❌ Déstructuration directe : perd la réactivité !
const { nom, age } = etat;
nom; // 'Alice' — mais ce n'est plus une ref réactive
// Si etat.nom change, nom ne se met pas à jour dans le template
// ✅ toRefs() : chaque propriété devient une ref
const { nom, age, score } = toRefs(etat);
nom.value; // 'Alice' — ref réactive liée à etat.nom
age.value; // 28 — ref réactive liée à etat.age
// Modifier via la ref ou l'objet reactive → même effet
nom.value = 'Bob'; // etat.nom === 'Bob' ✅
etat.age = 30; // age.value === 30 ✅
Usage dans setup()
const { reactive, computed, toRefs } = Vue;
createApp({
setup() {
const etat = reactive({
prenom: 'Alice',
nom: 'Dupont',
age: 28,
estActif: true
});
const nomComplet = computed(() => `${etat.prenom} ${etat.nom}`);
// Retourner avec toRefs pour exposer les propriétés individuellement
return {
...toRefs(etat), // prenom, nom, age, estActif — tous réactifs
nomComplet
};
// Équivalent à : return { prenom, nom, age, estActif, nomComplet }
// où chaque variable est une ref liée à etat
}
}).mount('#app');
toRef() — Variante pour une seule propriété
const { reactive, toRef } = Vue;
const etat = reactive({ nom: 'Alice', age: 28 });
// toRef() pour une seule propriété
const nomRef = toRef(etat, 'nom');
nomRef.value; // 'Alice'
nomRef.value = 'Bob'; // etat.nom === 'Bob' ✅
// Utile pour passer une propriété réactive à un composable
// ou en tant que prop d'un composant enfant
Récapitulatif Composition API
const { createApp, ref, reactive, computed, watch, watchEffect, toRefs } = Vue;
createApp({
setup() {
// État réactif
const compteur = ref(0);
const nom = ref('Alice');
const formulaire = reactive({ email: '', password: '' });
// Computed
const estValide = computed(() =>
formulaire.email.includes('@') && formulaire.password.length >= 6
);
// Watch
watch(compteur, (val, old) => {
console.log(`${old} → ${val}`);
});
// WatchEffect
watchEffect(() => {
document.title = `Compteur : ${compteur.value}`;
});
// Méthodes (fonctions normales)
function reinitialiser() {
compteur.value = 0;
formulaire.email = '';
formulaire.password = '';
}
// Retourner TOUT ce qui doit être accessible dans le template
return {
compteur,
nom,
...toRefs(formulaire),
estValide,
reinitialiser
};
}
}).mount('#app');