Les Composants Vue : Props & Réutilisabilité
Tu sais ce qui différencie un dev organisé d'un dev chaotique ? Les composants. C'est la base de tout en Vue, et si tu comprends pas ça, autant retourner faire du HTML statique en 2005. (ಠ_ಠ)
Objectifs du chapitre
À la fin de ce chapitre, tu seras capable de :
- ✅ Créer un composant Vue réutilisable
- ✅ Passer des données à un composant via les props
- ✅ Valider et typer tes props correctement
- ✅ Réutiliser un même composant avec des configurations différentes
Pourquoi créer des composants ?
Imagine que tu designs un site portfolio. Tu as des cartes de projet partout : sur la page d'accueil, sur la page projets, peut-être même sur la page contact.
Sans composants : Tu copies-colles le même bloc HTML 15 fois. Un jour, le client veut changer le border-radius. Tu dois modifier... 15 endroits. 💀
Avec composants : Tu crées UN composant ProjectCard.vue. Tu l'utilises 15 fois. Tu changes le border-radius UNE fois. Terminé.
C'est exactement comme les symboles dans Figma. Tu modifies le symbole maître, toutes les instances se mettent à jour. Même logique.

Anatomie d'un composant Vue

Un composant Vue, c'est un fichier .vue avec trois sections :
<script setup>
// La logique JavaScript
</script>
<template>
<!-- Le HTML de ton composant -->
<div class="ma-classe">
<p>Hello !</p>
</div>
</template>
<style scoped>
/* Le CSS (optionnel, privilégie Tailwind) */
</style>
Les règles d'or :
| Règle | Explication |
|---|---|
| Un seul élément racine | Ton <template> doit contenir UN élément parent qui englobe tout |
<script setup> |
La syntaxe moderne, plus simple que l'ancienne |
scoped sur le style |
Le CSS ne s'applique qu'à CE composant |
Créer et utiliser un composant
Étape 1 : Crée le fichier dans /src/components/
<!-- src/components/MonBouton.vue -->
<script setup>
// Rien pour l'instant
</script>
<template>
<button class="bg-blue-500 text-white px-4 py-2 rounded">
Clique ici
</button>
</template>
Étape 2 : Importe-le et utilise-le dans App.vue
<!-- src/App.vue -->
<script setup>
import MonBouton from './components/MonBouton.vue'
</script>
<template>
<div>
<h1>Ma page</h1>
<MonBouton />
<MonBouton />
<MonBouton />
</div>
</template>
⚠️ Important : Tu dois toujours importer tes composants avant de les utiliser !

Convention de nommage
| Format | Exemple | Utilisation |
|---|---|---|
| PascalCase (fichier) | MonBouton.vue |
Nom du fichier |
| PascalCase (template) | <MonBouton /> |
Dans le HTML |
💡 Conseil : Évite les noms génériques comme Button.vue ou Card.vue. Préfixe avec Base, App ou le nom de ton projet : BaseButton.vue, AppCard.vue.
Les Props : rendre un composant configurable
Ok, ton bouton fonctionne. Mais il affiche toujours "Clique ici". Pas très flexible. 🥴
Les props permettent de passer des données du parent vers l'enfant. C'est comme les paramètres d'une fonction.

Déclarer une prop
<!-- src/components/MonBouton.vue -->
<script setup>
defineProps({
text: String
})
</script>
<template>
<button class="bg-blue-500 text-white px-4 py-2 rounded">
{{ text }}
</button>
</template>
Utiliser le composant avec sa prop
<script setup>
import MonBouton from './components/MonBouton.vue'
</script>
<template>
<div class="flex gap-4">
<MonBouton text="Envoyer" />
<MonBouton text="Annuler" />
<MonBouton text="Supprimer" />
</div>
</template>
Résultat : 3 boutons avec des textes différents, même composant. 🎉


Syntaxe : avec ou sans :
C'est LA source d'erreur numéro 1 des débutants.
| Syntaxe | Ce qui est passé | Exemple |
|---|---|---|
prop="valeur" |
Une string littérale | text="Hello" → reçoit "Hello" |
:prop="valeur" |
Une expression JavaScript | :count="42" → reçoit le nombre 42 |
<!-- ❌ FAUX : "42" est une string -->
<MonComposant nombre="42" />
<!-- ✅ CORRECT : 42 est un number -->
<MonComposant :nombre="42" />
<!-- ✅ CORRECT : on passe une variable -->
<MonComposant :nombre="maVariable" />
<!-- ✅ CORRECT : les strings marchent sans ":" -->
<MonComposant text="Hello" />
La règle simple : Si c'est une string fixe → sans :. Sinon → avec :.

Valider ses props
Tu peux (et tu devrais) définir des règles pour tes props.
Syntaxe complète
<script setup>
defineProps({
// Juste le type
titre: String,
// Type + obligatoire
id: {
type: Number,
required: true
},
// Type + valeur par défaut
variant: {
type: String,
default: 'primary'
},
// Plusieurs types possibles
valeur: [String, Number],
// Validation personnalisée
taille: {
type: String,
validator: (value) => ['sm', 'md', 'lg'].includes(value)
}
})
</script>
Types disponibles
| Type | Exemple de valeur |
|---|---|
String |
"Hello" |
Number |
42 |
Boolean |
true / false |
Array |
['a', 'b', 'c'] |
Object |
{ nom: 'Alice', age: 25 } |
Exemple concret : Bouton avec variants
<!-- src/components/BaseButton.vue -->
<script setup>
import { computed } from 'vue'
const props = defineProps({
text: {
type: String,
required: true
},
variant: {
type: String,
default: 'primary',
validator: (v) => ['primary', 'secondary', 'danger'].includes(v)
}
})
const classes = computed(() => {
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700'
}
return variants[props.variant]
})
</script>
<template>
<button :class="classes" class="px-4 py-2 rounded font-medium">
{{ text }}
</button>
</template>
Utilisation :
<BaseButton text="Enregistrer" variant="primary" />
<BaseButton text="Annuler" variant="secondary" />
<BaseButton text="Supprimer" variant="danger" />
⚠️ Note : On utilise computed() ici pour calculer les classes dynamiquement. On verra ça en détail dans un prochain chapitre, pour l'instant fais-moi confiance !


Exercices pratiques
Exercice 1 : Badge de statut ⭐
Objectif : Créer ton premier composant avec props

Ta mission : Créer un composant StatusBadge.vue qui affiche un petit badge coloré.
Props à implémenter :
| Prop | Type | Requis | Description |
|---|---|---|---|
label |
String | ✅ Oui | Le texte du badge |
variant |
String | Non (défaut: "info") |
La couleur : "success", "warning", "error", "info" |
Couleurs attendues (Tailwind) :
| Variant | Classes |
|---|---|
success |
bg-green-100 text-green-800 |
warning |
bg-yellow-100 text-yellow-800 |
error |
bg-red-100 text-red-800 |
info |
bg-blue-100 text-blue-800 |
Résultat attendu :
<StatusBadge label="Publié" variant="success" />
<StatusBadge label="En attente" variant="warning" />
<StatusBadge label="Rejeté" variant="error" />
<StatusBadge label="Nouveau" /> <!-- Utilise "info" par défaut -->
Le badge doit avoir un style pill : rounded-full px-3 py-1 text-sm font-medium.
Exercice 2 : Carte de projet ⭐⭐
Objectif : Composant avec plusieurs props et réutilisation d'un autre composant

Ta mission : Créer un composant ProjectCard.vue pour afficher un projet de portfolio.
Props à implémenter :
| Prop | Type | Requis | Description |
|---|---|---|---|
title |
String | ✅ Oui | Titre du projet |
description |
String | ✅ Oui | Description courte |
image |
String | ✅ Oui | URL de l'image |
tags |
Array | Non (défaut: []) |
Liste des tags |
Structure HTML suggérée :
┌─────────────────────────────┐
│ [Image du projet] │
├─────────────────────────────┤
│ Titre du projet │
│ Description courte... │
│ │
│ [Tag 1] [Tag 2] [Tag 3] │
└─────────────────────────────┘
Contraintes :
- Utilise ton composant
StatusBadgepour afficher les tags (avecv-for) - L'image doit utiliser
object-coverpour garder ses proportions - N'oublie pas d'importer
StatusBadgedans ton composant !
Exemple d'utilisation :
<ProjectCard
title="Café Bohème"
description="Site vitrine pour un restaurant branché de Bruxelles"
image="/images/cafe-boheme.jpg"
:tags="['Web Design', 'Vue', 'Tailwind']"
/>
Exercice 3 : Avatar utilisateur ⭐⭐
Objectif : Gérer les cas conditionnels (image présente ou non)

Ta mission : Créer un composant UserAvatar.vue qui affiche soit une photo, soit les initiales de l'utilisateur.
Props à implémenter :
| Prop | Type | Requis | Description |
|---|---|---|---|
name |
String | ✅ Oui | Nom complet de l'utilisateur |
src |
String | Non | URL de la photo (optionnel) |
size |
String | Non (défaut: "md") |
Taille : "sm", "md", "lg" |
Comportement :
- Si
srcest fourni → afficher l'image - Si
srcest absent → afficher les initiales sur un fond coloré (utilisev-if/v-else)
Tailles :
| Size | Dimensions |
|---|---|
sm |
w-8 h-8 text-xs |
md |
w-12 h-12 text-sm |
lg |
w-16 h-16 text-base |
Exemple d'utilisation :
<!-- Avec image -->
<UserAvatar name="Alice Dupont" src="/photos/alice.jpg" size="lg" />
<!-- Sans image (affiche "AD") -->
<UserAvatar name="Alice Dupont" size="lg" />
<!-- Nom simple (affiche "B") -->
<UserAvatar name="Bob" size="sm" />
Aide pour extraire les initiales :
function getInitials(name) {
return name
.split(' ')
.map(word => word[0])
.join('')
.toUpperCase()
.slice(0, 2)
}
La suite...
Tu sais maintenant créer des composants configurables avec des props. Mais ils sont encore statiques — aucune interactivité.
Dans le prochain chapitre, on verra ref() pour rendre tes composants vivants : boutons qui comptent les clics, menus qui s'ouvrent et se ferment, toggles qui switchent... Bref, le fun commence. (ง'̀-'́)ง