Hoofdstuk 8 van 11

Vue 3-tutorial – componenten en props met defineProps

geverifieerd op 2 september 2026 · 5 min

Kort antwoord

Een component is een .vue-bestand met een eigen template, eigen state en eigen logica. Importeer het in <script setup> om het meteen in de template te gebruiken. De parent geeft er data aan door via props, gedeclareerd met defineProps en gebonden met een dubbele punt.

Aan het einde van dit hoofdstuk, kun je een interface opsplitsen in componenten en er data aan doorgeven.

Alles zit al zeven hoofdstukken lang in één bestand. Dat schaalt niet. Een component is een zelfstandig stuk interface, met een eigen template, eigen state en eigen logica, dat je hergebruikt en in elkaar nest.

De productfiche eruit halen

Maak src/components/ProductDisplay.vue aan en verhuis daarheen alles wat met het product te maken heeft: de afbeelding, de titel, de beschikbaarheid, de details, de kleurbolletjes, de knop.

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>

De winkelwagen gaat niet mee: die hoort niet bij het product. App.vue krimpt tot:

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>

Een component die je in <script setup> importeert, kun je meteen in de template gebruiken: een extra declaratie is niet nodig.

De knop vult de winkelwagen niet meer

Dat klopt, en het is het onderwerp van het volgende hoofdstuk. cart leeft nu in de parent, buiten bereik van het kindcomponent. We hebben trouwens de @click van de knop weggehaald: die laten staan zou een fout geven, want addToCart bestaat niet meer in dit component.

Een component benoemen

Twee conventies bestaan naast elkaar. In PascalCase, <ProductDisplay />, zie je in één oogopslag het verschil tussen een component en een HTML-element, en kan je editor naar de definitie springen. In kebab-case, <product-display></product-display>, blijf je compatibel met een template die rechtstreeks in een HTML-pagina staat.

Kies in een .vue-bestand voor PascalCase.

Een component heeft zijn eigen scope

Zet er in één keer drie fiches neer:

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

Er verschijnen drie onafhankelijke fiches: over een bolletje van de eerste zweven laat de andere twee onberoerd. Elke instantie voert zijn eigen <script setup> uit en heeft zijn eigen selectedVariant.

Elke fiche houdt ook zijn eigen geselecteerde variant bij.

Dat is meteen de reden waarom de state van een component in een functie wordt gedeclareerd. Was variants een gewoon object dat op moduleniveau werd gedeeld, dan zouden de drie fiches elkaar in de weg zitten.

Haal de twee overtollige fiches weg voor je verdergaat.

De props

Die geïsoleerde scope roept een vraag op: hoe geeft de parent informatie door aan het kind? Via een prop, een eigen attribuut dat het component verklaart te accepteren.

Laten we de verzending toevoegen, gratis voor premiumklanten. De informatie hoort bij de parent:

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

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

En het kind declareert haar:

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 is in <script setup> beschikbaar zonder import: het is een macro van de compiler, geen gewone functie.

In de template schrijf je gewoon {{ premium }}. In het script loopt het via het teruggegeven object: props.premium.

Props serieus declareren

Met de lange vorm kun je valideren:

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
  }
})

De standaardwaarde van een array of een object moet door een functie worden teruggegeven, anders zouden alle instanties hetzelfde object delen.

De dubbele punt is verplicht

:premium="premium" geeft de waarde van de variabele door. premium="premium", zonder dubbele punt, geeft de string "premium" door. Omdat een niet-lege string waar is, blijft de bug onopgemerkt tot de dag dat de waarde onwaar zou moeten zijn. Vue waarschuwt trouwens in de console dat het type Boolean verwachtte en String kreeg.

Props gaan omlaag, nooit omhoog

Een prop stroomt maar één kant op: van de parent naar het kind. Het kind mag hem niet wijzigen, Vue toont een waarschuwing als je het toch probeert.

Heeft het kind een lokale, aanpasbare versie nodig, kopieer die dan in een ref. Moet het een verandering bij de parent uitlokken, dan moet het een signaal sturen: dat is het onderwerp van het volgende hoofdstuk.

In de Options API

Props declareer je in de optie props, met dezelfde validatiesyntaxis, en lees je uit via this.premium. Kindcomponenten moeten bovendien in de optie components worden geregistreerd.

OefeningHaal de lijst met details naar een component ProductDetails.vue dat de array als prop krijgt. Toon daarna twee productfiches en controleer dat ze elk hun eigen geselecteerde variant hebben.

Veelgemaakte fouten

De dubbele punt op een prop vergeten premium="premium" geeft de string ‘premium’ door, die altijd waar is. Schrijf :premium="premium". Vue meldt trouwens dat het type Boolean verwachtte en String kreeg.
Een prop wijzigen in het kind Props gaan alleen omlaag, Vue waarschuwt in de console. Kopieer de waarde in een lokale ref, of stuur een event omhoog.
Standaardwaarde van een array of een object Die moet door een functie worden teruggegeven, anders delen alle instanties hetzelfde object.
Nieuwsbrief

Nieuwe tests, tutorials en projecten, per e-mail.

Reproduceerbare tests, geversioneerde code, gedateerde resultaten. Nooit spam.