Chapitre 8 sur 11

Tutoriel VueJS 3 – Composants et props

vérifié le 2 septembre 2026 · 5 min

Réponse rapide

Un composant est un fichier .vue avec son gabarit, son état et sa logique. Importez-le dans <script setup> pour l’utiliser directement dans le gabarit. Le parent lui transmet des données par des props, déclarés avec defineProps et liés avec les deux-points.

À la fin de ce chapitre, vous saurez découper une interface en composants et leur transmettre des données.

Tout tient dans un seul fichier depuis sept chapitres. Ça ne passe pas l’échelle. Un composant est un morceau d’interface autonome, avec son gabarit, son état et sa logique, qu’on réutilise et qu’on imbrique.

Extraire la fiche produit

Créez src/components/ProductDisplay.vue et déplacez-y tout ce qui concerne le produit : l’image, le titre, la disponibilité, les détails, les pastilles, le bouton.

src/components/ProductDisplay.vue
<script setup>
import { computed, ref } from 'vue'

const product = ref('T-Shirt')
const brand = ref('Gekkode')
const selectedVariant = ref(0)
const details = ref(['60 % coton', '30 % laine', '10 % polyester'])
const variants = ref([
  { id: 2234, color: '#2563eb', image: '/images/t-shirt-bleu.svg', quantity: 20 },
  { id: 2235, color: '#dc2626', image: '/images/t-shirt-rouge.svg', quantity: 0 }
])

const title = computed(() => `${brand.value} ${product.value}`)
const image = computed(() => variants.value[selectedVariant.value].image)
const inStock = computed(() => variants.value[selectedVariant.value].quantity > 0)

function updateVariant(index) {
  selectedVariant.value = index
}
</script>

<template>
  <div class="product-display">
    <div class="product-image">
      <img :src="image" :alt="title">
    </div>
    <div class="product-info">
      <h1>{{ title }}</h1>
      <p v-if="inStock">En stock</p>
      <p v-else>En rupture</p>
      <ul>
        <li v-for="detail in details" :key="detail">{{ detail }}</li>
      </ul>
      <div class="variants-wrapper">
        <div
          v-for="(variant, index) in variants"
          :key="variant.id"
          class="color-circle"
          :style="{ backgroundColor: variant.color }"
          @mouseover="updateVariant(index)"
        ></div>
      </div>
      <button class="button" :class="{ disabledButton: !inStock }" :disabled="!inStock">
        Ajouter au panier
      </button>
    </div>
  </div>
</template>

Le panier ne part pas avec : il n’appartient pas au produit. App.vue se réduit à :

src/App.vue
<script setup>
import { ref } from 'vue'
import ProductDisplay from './components/ProductDisplay.vue'

const cart = ref(0)
</script>

<template>
  <div class="cart">Panier ({{ cart }})</div>
  <ProductDisplay />
</template>

Un composant importé dans <script setup> est directement utilisable dans le gabarit : pas de déclaration supplémentaire.

Le bouton ne remplit plus le panier

C’est normal, et c’est le sujet du chapitre suivant. cart vit maintenant dans le parent, hors de portée du composant enfant. Nous avons d’ailleurs retiré le @click du bouton : le laisser aurait produit une erreur, addToCart n’existant plus dans ce composant.

Nommer un composant

Deux conventions coexistent. En PascalCase, <ProductDisplay />, on distingue au coup d’œil un composant d’une balise HTML, et l’éditeur peut naviguer vers sa définition. En kebab-case, <product-display></product-display>, on reste compatible avec un gabarit écrit directement dans une page HTML.

Dans un fichier .vue, préférez le PascalCase.

Un composant a sa propre portée

Ajoutez trois fiches d’un coup :

src/App.vue
<template>
  <ProductDisplay />
  <ProductDisplay />
  <ProductDisplay />
</template>

Trois fiches apparaissent, indépendantes : survoler une pastille de la première ne touche pas les autres. Chaque instance exécute son propre <script setup> et possède son propre selectedVariant.

Chaque fiche garde aussi sa propre déclinaison sélectionnée.

C’est aussi la raison pour laquelle l’état d’un composant se déclare dans une fonction. Si variants était un simple objet partagé au niveau du module, les trois fiches se marcheraient dessus.

Retirez les deux fiches en trop avant de continuer.

Les props

La portée isolée pose une question : comment le parent transmet-il une information à l’enfant ? Par un prop, un attribut personnalisé que le composant déclare accepter.

Ajoutons la livraison, gratuite pour les clients premium. L’information appartient au parent :

src/App.vue
<script setup>
const premium = ref(true)
</script>

<template>
  <div class="cart">Panier ({{ cart }})</div>
  <ProductDisplay :premium="premium" />
</template>

Et l’enfant la déclare :

src/components/ProductDisplay.vue
<script setup>
const props = defineProps({
  premium: { type: Boolean, required: true }
})

const shipping = computed(() => (props.premium ? 'Gratuite' : '2,99 €'))
</script>

<template>
  <p>Livraison : {{ shipping }}</p>
</template>

defineProps est disponible sans import dans <script setup> : c’est une macro du compilateur, pas une fonction ordinaire.

Dans le gabarit, on écrit {{ premium }} directement. Dans le script, il faut passer par l’objet renvoyé : props.premium.

Déclarer les props sérieusement

La forme longue permet de valider :

javascript
const props = defineProps({
  premium: { type: Boolean, required: true },
  titre: { type: String, default: 'Sans titre' },
  tailles: { type: Array, default: () => [] },
  note: {
    type: Number,
    validator: (valeur) => valeur >= 0 && valeur <= 5
  }
})

La valeur par défaut d’un tableau ou d’un objet doit être renvoyée par une fonction, sinon toutes les instances partageraient le même objet.

Deux-points obligatoires

:premium="premium" transmet la valeur de la variable. premium="premium", sans deux-points, transmet la chaîne "premium". Comme une chaîne non vide est vraie, le bug passe inaperçu jusqu’au jour où la valeur devrait être fausse. Vue avertit d’ailleurs dans la console d’un type attendu Boolean reçu String.

Les props descendent, et ne remontent pas

Un prop circule dans un seul sens : du parent vers l’enfant. L’enfant ne doit pas le modifier, Vue affiche un avertissement si vous essayez.

Si l’enfant a besoin d’une version locale modifiable, copiez-la dans un ref. S’il doit provoquer un changement chez le parent, il faut lui envoyer un signal : c’est l’objet du chapitre suivant.

En Options API

Les props se déclarent dans l’option props, avec la même syntaxe de validation, et se lisent par this.premium. Les composants enfants doivent en plus être enregistrés dans l’option components.

ExerciceSortez la liste des détails dans un composant ProductDetails.vue qui reçoit le tableau en prop. Puis affichez deux fiches produit et vérifiez qu’elles ont bien chacune leur propre déclinaison sélectionnée.

Erreurs fréquentes

Oublier les deux-points sur un prop premium="premium" transmet la chaîne « premium », toujours vraie. Écrivez :premium="premium". Vue signale d’ailleurs un type Boolean attendu, String reçu.
Modifier un prop dans l’enfant Les props descendent seulement, Vue avertit dans la console. Copiez la valeur dans un ref local, ou remontez un événement.
Valeur par défaut d’un tableau ou d’un objet Elle doit être renvoyée par une fonction, sinon toutes les instances partagent le même objet.
Newsletter

Les nouveaux tests, tutoriels et projets, par e-mail.

Tests reproductibles, code versionné, résultats datés. Jamais de spam.