Formation Vue 3 | Transmettre des Données entre Composants
Apprenez à transmettre des données entre composants Vue grâce aux props, événements (emits) et slots.
Props
Principe
S’il n’était pas possible de passer des informations sur-mesure à un composant, alors leur utilisation serait limitée…
Imaginons avoir par exemple un composant SuperAlert
dont le rôle est d’afficher un message d’alerte sur une page.
<template>
<div class="super-alert">
Mot de passe erroné
</div>
</template>
<style scoped>
.super-alert {
padding: .75rem 1rem;
background-color: red;
border-radius: 5px;
}
</style>
<script>
import SuperAlert from './SuperAlert.vue'
export default {
components: {
SuperAlert
},
}
</script>
<template>
<SuperAlert>
</template>
Ici, le message de l’alerte est écrit en dur, il restera ainsi toujours Mot de passe erroné
… De même pour la couleur de fond qui sera toujours red
. L’exploitation et la réutilisation de ce composant est très discutable !
Pour pallier à cela, nous allons pouvoir transmettre des données au composant lors de son appel via des props.
Les props sont des attributs spécifiques aux composants qui permettent de transférer des données au composant.
Envoi de props (parent)
Pour envoyer des données statiques (chaîne de caractères), il suffit de déclarer votre prop comme un attribut classique et de lui associer la valeur à transmettre au composant.
<template>
<SuperAlert
message="Vous avez un nouveau message."
bg-color="green"
text-color="#fff"
/>
<SuperAlert
message="Mauvais mot de passe."
bg-color="red"
text-color="#fff"
/>
</template>
Pour envoyer des données dynamiques (expression JavaScript), il faudra alors déclarer votre attribut en le « bindant » avec v-bind
ou :
.
<template>
<SuperAlert
:message="alert.message"
:bg-color="alert.bgColor"
:text-color="alert.textColor"
/>
</template>
<script>
import SuperAlert from './SuperAlert.vue'
export default {
data() {
return {
alert: {
message: "Test",
bgColor: "green",
textColor: "#fff"
}
}
},
components: {
SuperAlert
},
}
</script>
Réception des props (enfant)
Pour réceptionner les données passées en props, il suffit de déclarer dans les options du composant la nouvelle propriété props
. Cette propriété peut simplement contenir dans un tableau de chaînes de caractères les props passées.
<script>
export default {
props: ['message', 'bgColor', 'textColor'],
}
</script>
<template>
<div class="super-alert" :style="{ backgroundColor: bgColor, color: textColor }">
{{ message }}
</div>
</template>
<style scoped>
.super-alert {
padding: .75rem 1rem;
border-radius: 5px;
}
</style>
En revanche, il est préférable d’être plus rigoureux en détaillant pour chaque prop le type de valeur attendu. On parle de validation.
<script>
export default {
props: {
message: String,
bgColor: String,
textColor: String
},
}
</script>
<template>
<div class="super-alert" :style="{ backgroundColor: bgColor, color: textColor }">
{{ message }}
</div>
</template>
<style scoped>
.super-alert {
padding: .75rem 1rem;
border-radius: 5px;
}
</style>
Aller plus loin avec les validations
Check de type :
export default {
props: {
// (`null` and `undefined` values will allow any type)
propA: String,
// Multiple possible types
propB: [String, Number],
}
}
Prop requise :
export default {
props: {
// Required string
propC: {
type: String,
required: true
},
}
}
Valeur par défaut :
export default {
props: {
// Number with a default value
propD: {
type: Number,
default: 100
},
// Object with a default value
propE: {
type: Object,
// Object or array defaults must be returned from
// a factory function. The function receives the raw
// props received by the component as the argument.
default(rawProps) {
return { message: 'hello' }
}
}
}
}
Validateur personnalisé :
export default {
props: {
// Custom validator function
propF: {
validator(type) {
// The type must match one of these strings
return ['success', 'warning', 'danger'].includes(type)
}
}
}
}
Quelques remarques :
- Toutes les props sont facultatives par défaut, sauf si
required: true
est spécifié. - Une prop facultative aura une valeur
undefined
(hormisBoolean
qui vaudrafalse
).
Evènements (emits)
Principe
Parfois, il peut être nécessaire pour un composant enfant de communiquer avec son composant parent : cette mécanique est rendue possible par ce que l’on appelle les « emits ».
Cette communication se traduit la plupart du temps par le besoin de transmettre des données à un composant parent.
Concrètement :
- Un composant enfant émet un évènement
- Le composant parent est notifié lors de l’émission d’un évènement
Emettre un évènement (enfant)
Pour émettre un évènement depuis le composant enfant on utilise la méthode $emit()
, dans laquelle on renseigne le nom d’un évènement sur mesure que l’on souhaite écouter.
Cela peut se faire directement depuis le template :
<template>
<button @click="$emit('myCustomEvent')">Cliquez moi</button>
</template>
Cela peut aussi se faire depuis un script avec this
:
<template>
<button @click="demo()">Cliquez moi</button>
</template>
<script>
export default {
methods: {
demo() {
this.$emit('myCustomEvent')
}
}
}
</script>
À chaque click
sur le bouton, l’évènement myCustomEvent
sera émis au composant parent. Le composant parent sera notifié que l’évènement a été déclenché dans le composant enfant et pourra réagir en fonction.
Dans le cas où vous souhaiteriez également transmettre au composant parent des données, il sera possible de les passer via des arguments additionnels à la méthode $emit()
.
$emit('myCustomEvent', data1, data2, ...)
Ecouter l’évènement émis (parent)
Du côté du composant parent, on écoute l’évènement émis par le composant enfant et on exécute la logique voulue lorsqu’il est déclenché.
<template>
<ChildComponent @my-custom-event="..."/>
</template>
<script>
import ChildComponent from '@/components/ChildComponent.vue'
export default {
components: {
ChildComponent
},
data() {
return {
clicks: 0
}
}
}
</script>
Si vous souhaitez récupérer des données transmises par l’enfant avec par exemple $emit('myCustomEvent', 1)
, vous avez le choix entre :
1. Une fonction fléchée au sein du template
<ChildComponent @my-custom-event="(value) => clicks += value" />
2. Une méthode au sein des scripts
<ChildComponent @my-custom-event="maMethode" />
Ensuite, la valeur sera automatiquement passée en tant que paramètre de cette méthode :
methods: {
maMethode(value) {
this.clicks += value
}
}
Slots
Principe
Si nous devions faire passer l’intégralité des informations à notre composant, comme par exemple son contenu, via des props, cela s’avèrerait parfois fastidieux et/ou limitant. Reprenons l’exemple de notre composant d’alerte :
<SuperAlert
message="Vous avez un nouveau message."
bg-color="green"
text-color="#fff"
/>
Les slots consistent à délimiter des zones avec les balises <slot></slot>
dans un composant.
<template>
<div class="super-alert" :style="{ backgroundColor: bgColor, color: textColor }">
<slot></slot>
</div>
</template>
Désormais, lors de l’usage de ce composant, il est possible de passer du contenu (textuel ou HTML) au slot, en l’insérant au sein des balises paires.
<SuperAlert
bg-color="green"
text-color="#fff"
>
<i class="fas fa-exclamation-circle"></i>
Vous avez un nouveau message.
</SuperAlert>
Cela nous permet ainsi de nous passer de la prop message
et d’insérer du contenu dynamiquement au sein du composant. C’est finalement la manière naturelle de transmettre du contenu à une balise HTML, et ça, c’est plus propre !
Contenu par défaut
Il est également possible de spécifier dans le composant un contenu par défaut qui s’affichera si aucun contenu n’est spécifié à l’utilisation d’un composant :
<SuperAlert
bg-color="green"
text-color="#fff"
>
</SuperAlert>
<template>
<div class="super-alert" :style="{ backgroundColor: bgColor, color: textColor }">
<slot>Contenu par défaut</slot>
</div>
</template>
Slots nommés
Il est tout à fait possible de déclarer plusieurs slot au sein d’un même composant. Prenons l’exemple d’un composant de type accordion
utilisé dans le cadre d’une FAQ. On donne à l’utilisateur du composant la possibilité de spécifier un titre ainsi qu’une description de FAQ en faisant usage de 2 slots distincts.
Chaque slot devra alors être identifié. Cette identification se fera via l’usage d’un attribut name
.
<template>
<div class="super-accordion">
<header class="super-accordion-header" @click="opened=!opened">
<h3><slot name="title"></slot></h3>
</header>
<div class="super-accordion-body" v-if="opened">
<p><slot name="description"></slot></p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
opened: false
}
}
}
</script>
Ainsi, côté composant parent, nous avons la possibilité de préciser quel contenu doit s’insérer dans quelle zone en combinant la balise <template>
avec la directive v-slot
.
<template>
<SuperAccordion>
<template v-slot:title>Peut-on retourner un article ?</template>
<template v-slot:description>Vous avez 30 jours à compter de la réception de votre commande pour... blablabla.</template>
</SuperAccordion>
</template>
La directive v-slot:
est généralement raccourcie par le caractère #
, donc <template v-slot:title>
peut être simplifié par juste <template #title>
.
Lorsqu’un composant accepte un slot par défaut et des slots nommés, tous les nœuds HTML n’étant pas situés au sein d’une balise <template>
seront considérés comme destinés au slot par défaut.
<template>
<div class="super-accordion">
<header class="super-accordion-header" @click="opened=!opened">
<h3><slot name="title"></slot></h3>
</header>
<div class="super-accordion-body" v-if="opened">
<p><slot></slot></p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
opened: false
}
}
}
</script>
<style scoped>
.super-accordion {
border: solid #000 1px;
}
.super-accordion-header {
background-color: #eee;
}
.super-accordion-header, .super-accordion-body {
padding: .5rem 1rem;
}
</style>
<template>
<SuperAccordion>
<template #title>Peut-on retourner un article ?</template>
Vous avez 30 jours à compter de la réception... <!-- Va dans le second slot, non nommé -->
</SuperAccordion>
</template>
Préprocesseurs, supersets et moteurs de templates
Qu’il s’agisse de HTML, CSS ou encore JS, les langages front web se voient souvent “dopés” par l’utilisation de technologies qui simplifient, dynamisent ou encore sécurisent leur écriture.
- Pour HTML, on parle de moteurs de templates tel que Pug.
- Pour CSS, on parle de préprocesseurs tels que Sass, Less ou encore Stylus.
- Pour JavaScript, on parle de superset tel que TypeScript.
Pour activer le support de la technologie en question, il faut :
- Installer et configurer les préprocesseurs en question via le gestionnaire de paquet et l’outil de build de votre choix (Vite, Webpack…).
- Ajouter l’attribut
lang
au sein des balises<template>
,<script>
ou<style>
avec pour valeur, le préprocesseur à utiliser.
Quelques exemples :
Support de Pug
<template lang="pug">
...
</template>
Support de Sass
<style lang="scss">
...
</style>
Support de TypeScript
<script lang="ts">
...
</script>