Composants : props, emits, slots, v-model
Le composant est une fonction avec un contrat
Un composant Vue = un fichier .vue (template + script + style scopé) qui expose une interface :
<script setup lang="ts">
const props = defineProps<{ lesson: Lesson; done?: boolean }>() // â entrĂ©es typĂ©es
const emit = defineEmits<{ toggle: [done: boolean] }>() // â Ă©vĂ©nements typĂ©s
</script>
<template>
<button :class="{ done: props.done }" @click="emit('toggle', !props.done)">
{{ lesson.title }}
<slot name="badge" /> <!-- contenu injecté -->
</button>
</template>
Les trois canaux, et leur sens unique :
- props : les donnĂ©es descendent du parent â jamais mutĂ©es par l'enfant (le one-way data flow : la donnĂ©e a UN propriĂ©taire) ;
- emits : les Ă©vĂ©nements remontent â l'enfant signale, le parent dĂ©cide. C'est le pattern qu'on a appliquĂ© dans les composants de cette plateforme :
QuizQuestionĂ©metchecked,FlashcardĂ©metgradedâ les molĂ©cules ne savent pas ce que le parent fera ; - slots : le parent injecte du contenu DANS l'enfant â l'inversion de contrĂŽle du markup (le composant fournit la structure, le parent le contenu).
v-model : le sucre bidirectionnel (qui reste unidirectionnel)
v-model sur un composant = :modelValue + @update:modelValue â le flux reste descendant + Ă©vĂ©nement, juste raccourci. Un input contrĂŽlĂ© s'Ă©crit ainsi, et un composant de formulaire expose son propre v-model en Ă©mettant update:modelValue.
Les rÚgles de conception qui tiennent à l'échelle
- props minimales : un composant qui reçoit 15 props fait trop de choses â dĂ©couper ;
- l'enfant ignore son contexte : il Ă©met des intentions, ne connaĂźt ni le store ni l'API (testable en isolation â nos tests de
QuizQuestionmontent le composant seul) ; - atomic design (utilisĂ© sur cette plateforme mĂȘme) : atoms (bouton, badge) â molecules (carte de leçon) â organisms (le sommaire) â la taille du contrat croĂźt avec l'Ă©tage, l'accĂšs aux stores/API est rĂ©servĂ© aux Ă©tages hauts.
Pourquoi l'enfant ne doit-il jamais muter une prop ?