Aller au contenu

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 StatusBadge pour afficher les tags (avec v-for)
  • L'image doit utiliser object-cover pour garder ses proportions
  • N'oublie pas d'importer StatusBadge dans 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 src est fourni → afficher l'image
  • Si src est absent → afficher les initiales sur un fond coloré (utilise v-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. (ง'̀-'́)ง