Utilisation de slots dans Vue.js

Publié: 2022-03-10
Résumé rapide ↬ Les slots sont un outil puissant pour créer des composants réutilisables dans Vue.js, bien qu'ils ne soient pas la fonctionnalité la plus simple à comprendre. Voyons comment utiliser les slots et quelques exemples de la façon dont ils peuvent être utilisés dans vos applications Vue.

Avec la récente version de Vue 2.6, la syntaxe d'utilisation des slots a été rendue plus succincte. Ce changement apporté aux machines à sous m'a redonné envie de découvrir la puissance potentielle des machines à sous pour fournir une réutilisabilité, de nouvelles fonctionnalités et une lisibilité plus claire à nos projets basés sur Vue. De quoi les machines à sous sont-elles vraiment capables ?

Si vous êtes nouveau sur Vue ou si vous n'avez pas vu les changements par rapport à la version 2.6, lisez la suite. La meilleure ressource pour en savoir plus sur les machines à sous est probablement la propre documentation de Vue, mais je vais essayer de donner un aperçu ici.

Que sont les machines à sous ?

Les slots sont un mécanisme pour les composants Vue qui vous permet de composer vos composants d'une manière autre que la stricte relation parent-enfant. Les machines à sous vous permettent de placer du contenu dans de nouveaux emplacements ou de rendre les composants plus génériques. La meilleure façon de les comprendre est de les voir en action. Commençons par un exemple simple :

 // frame.vue <template> <div class="frame"> <slot></slot> </div> </template>

Ce composant a un wrapper div . Supposons que div soit là pour créer un cadre stylistique autour de son contenu. Ce composant peut être utilisé de manière générique pour envelopper un cadre autour du contenu de votre choix. Voyons à quoi ça ressemble de l'utiliser. Le composant frame fait ici référence au composant que nous venons de créer ci-dessus.

 // app.vue <template> <frame><img src="an-image.jpg"></frame> </template>

Le contenu qui se trouve entre les balises de frame d'ouverture et de fermeture sera inséré dans le composant frame où se trouve l' slot , remplaçant les balises d' slot . C'est la façon la plus élémentaire de le faire. Vous pouvez également spécifier le contenu par défaut à insérer dans un emplacement simplement en le remplissant :

 // frame.vue <template> <div class="frame"> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>

Alors maintenant, si nous l'utilisons comme ceci à la place :

 // app.vue <template> <frame /> </template>

Le texte par défaut de "Ceci est le contenu par défaut si rien n'est spécifié pour aller ici" apparaîtra, mais si nous l'utilisons comme nous le faisions auparavant, le texte par défaut sera remplacé par la balise img .

Plus après saut! Continuez à lire ci-dessous ↓

Emplacements multiples/nommés

Vous pouvez ajouter plusieurs emplacements à un composant, mais si vous le faites, tous sauf un doivent avoir un nom. S'il y en a un sans nom, c'est l'emplacement par défaut. Voici comment créer plusieurs emplacements :

 // titled-frame.vue <template> <div class="frame"> <header><h2><slot name="header">Title</slot></h2></header> <slot>This is the default content if nothing gets specified to go here</slot> </div> </template>

Nous avons conservé le même emplacement par défaut, mais cette fois nous avons ajouté un emplacement nommé header dans lequel vous pouvez saisir un titre. Vous l'utilisez comme ceci :

 // app.vue <template> <titled-frame> <template v-slot:header> <!-- The code below goes into the header slot --> My Image's Title </template> <!-- The code below goes into the default slot --> <img src="an-image.jpg"> </titled-frame> </template>

Comme avant, si nous voulons ajouter du contenu à l'emplacement par défaut, placez-le simplement directement dans le composant titled-frame . Cependant, pour ajouter du contenu à un emplacement nommé, nous devions encapsuler le code dans une balise de template avec une directive v-slot . Vous ajoutez deux-points ( : ) après v-slot , puis écrivez le nom de l'emplacement auquel vous souhaitez que le contenu soit transmis. Notez que v-slot est nouveau dans Vue 2.6, donc si vous utilisez une version plus ancienne, vous devrez lire la documentation sur la syntaxe obsolète des slots.

Machines à sous délimitées

Une autre chose que vous devez savoir est que les machines à sous peuvent transmettre des données/fonctions à leurs enfants. Pour le démontrer, nous aurons besoin d'un composant d'exemple complètement différent avec des slots, un qui est encore plus artificiel que le précédent : copions en quelque sorte l'exemple de la documentation en créant un composant qui fournit les données sur l'utilisateur actuel à ses slots :

 // current-user.vue <template> <span> <slot v-bind:user="user"> {{ user.lastName }} </slot> </span> </template> <script> export default { data () { return { user: ... } } } </script>

Ce composant a une propriété appelée user avec des détails sur l'utilisateur. Par défaut, le composant affiche le nom de famille de l'utilisateur, mais notez qu'il utilise v-bind pour lier les données de l'utilisateur à l'emplacement. Avec cela, nous pouvons utiliser ce composant pour fournir les données utilisateur à son descendant :

 // app.vue <template> <current-user> <template v-slot:default="slotProps">{{ slotProps.user.firstName }}</template> </current-user> </template>

Pour accéder aux données transmises au slot, nous spécifions le nom de la variable de portée avec la valeur de la directive v-slot .

Il y a quelques notes à prendre ici :

  • Nous avons spécifié le nom de default , bien que nous n'en ayons pas besoin pour l'emplacement par défaut. Au lieu de cela, nous pourrions simplement utiliser v-slot="slotProps" .
  • Vous n'avez pas besoin d'utiliser slotProps comme nom. Tu peux appeler ça comme tu le veux.
  • Si vous n'utilisez qu'un emplacement par défaut, vous pouvez ignorer cette balise template interne et placer la directive v-slot directement sur la balise current-user .
  • Vous pouvez utiliser la déstructuration d'objet pour créer des références directes aux données d'emplacement délimitées plutôt que d'utiliser un seul nom de variable. En d'autres termes, vous pouvez utiliser v-slot="{user}" au lieu de v-slot="slotProps" et ensuite vous pouvez utiliser user directement au lieu de slotProps.user .

En tenant compte de ces notes, l'exemple ci-dessus peut être réécrit comme ceci :

 // app.vue <template> <current-user v-slot="{user}"> {{ user.firstName }} </current-user> </template>

Quelques autres choses à garder à l'esprit :

  • Vous pouvez lier plusieurs valeurs avec les directives v-bind . Donc, dans l'exemple, j'aurais pu faire plus que simplement user .
  • Vous pouvez également transmettre des fonctions à des slots délimités. De nombreuses bibliothèques l'utilisent pour fournir des composants fonctionnels réutilisables, comme vous le verrez plus tard.
  • v-slot a un alias de # . Ainsi, au lieu d'écrire v-slot:header="data" , vous pouvez écrire #header="data" . Vous pouvez également simplement spécifier #header au lieu de v-slot:header lorsque vous n'utilisez pas d'emplacements délimités. Comme pour les emplacements par défaut, vous devrez spécifier le nom de default lorsque vous utilisez l'alias. En d'autres termes, vous devrez écrire #default="data" au lieu de #="data" .

Il y a quelques points mineurs supplémentaires que vous pouvez découvrir dans les documents, mais cela devrait suffire à vous aider à comprendre de quoi nous parlons dans le reste de cet article.

Que pouvez-vous faire avec les machines à sous ?

Les machines à sous n'ont pas été conçues dans un seul but, ou du moins si elles l'étaient, elles ont évolué bien au-delà de cette intention initiale d'être un outil puissant pour faire beaucoup de choses différentes.

Patrons réutilisables

Les composants ont toujours été conçus pour pouvoir être réutilisés, mais certains modèles ne sont pas pratiques à appliquer avec un seul composant "normal" car le nombre d' props dont vous aurez besoin pour le personnaliser peut être excessif ou vous auriez besoin de passer de grandes sections de contenu et potentiellement d'autres composants à travers les props . Les slots peuvent être utilisés pour englober la partie "extérieure" du modèle et permettre à d'autres HTML et/ou composants d'être placés à l'intérieur de ceux-ci pour personnaliser la partie "intérieur", permettant au composant avec des slots de définir le modèle et les composants injectés dans le slots pour être unique.

Pour notre premier exemple, commençons par quelque chose de simple : un bouton. Imaginez que vous et votre équipe utilisiez Bootstrap*. Avec Bootstrap, vos boutons sont souvent associés à la classe de base "btn" et à une classe spécifiant la couleur, telle que "btn-primary". Vous pouvez également ajouter une classe de taille, telle que `btn-lg`.

* Je ne vous encourage ni ne vous déconseille de faire cela, j'avais juste besoin de quelque chose pour mon exemple et c'est assez connu.

Supposons maintenant, par souci de simplicité, que votre application/site utilise toujours btn-primary et btn-lg . Vous ne voulez pas toujours avoir à écrire les trois classes sur vos boutons, ou peut-être que vous ne faites pas confiance à une recrue pour se souvenir de faire les trois. Dans ce cas, vous pouvez créer un composant qui possède automatiquement ces trois classes, mais comment autorisez-vous la personnalisation du contenu ? Un prop n'est pas pratique car une balise de button est autorisée à contenir toutes sortes de HTML, nous devons donc utiliser un slot.

 <!-- my-button.vue --> <template> <button class="btn btn-primary btn-lg"> <slot>Click Me!</slot> </button> </template>

Maintenant, nous pouvons l'utiliser partout avec le contenu que vous voulez :

 <!-- somewhere else, using my-button.vue --> <template> <my-button> <img src="/img/awesome-icon.jpg"> SMASH THIS BUTTON TO BECOME AWESOME FOR ONLY $500!!! </my-button> </template>

Bien sûr, vous pouvez utiliser quelque chose de beaucoup plus gros qu'un bouton. S'en tenir à Bootstrap, regardons un modal, ou du moins la partie HTML ; Je n'aborderai pas la fonctionnalité… pour le moment.

 <!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <slot name="footer"></slot> </div> </div> </div> </div> </template>

Maintenant, utilisons ceci :

 <!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <template #footer> <em>Now back to your regularly scheduled app usage</em> </template> </my-modal> </template>

Le type de cas d'utilisation ci-dessus pour les machines à sous est évidemment très utile, mais il peut faire encore plus.

Réutilisation des fonctionnalités

Les composants Vue ne concernent pas uniquement le HTML et le CSS. Ils sont construits avec JavaScript, ils sont donc également axés sur la fonctionnalité. Les emplacements peuvent être utiles pour créer une fonctionnalité une fois et l'utiliser à plusieurs endroits. Reprenons notre exemple modal et ajoutons une fonction qui ferme le modal :

 <!-- my-modal.vue --> <template> <div class="modal" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <slot name="header"></slot> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <slot name="body"></slot> </div> <div class="modal-footer"> <!-- using `v-bind` shorthand to pass the `closeModal` method to the component that will be in this slot --> <slot name="footer" :closeModal="closeModal"></slot> </div> </div> </div> </div> </template> <script> export default { //... methods: { closeModal () { // Do what needs to be done to close the modal... and maybe remove it from the DOM } } } </script>

Maintenant, lorsque vous utilisez ce composant, vous pouvez ajouter un bouton au pied de page qui peut fermer le modal. Normalement, dans le cas d'un modal Bootstrap, vous pouvez simplement ajouter data-dismiss="modal" à un bouton, mais nous souhaitons masquer les éléments spécifiques à Bootstrap des composants qui seront insérés dans ce composant modal. Nous leur transmettons donc une fonction qu'ils peuvent appeler et ils ne sont pas plus conscients de l'implication de Bootstrap :

 <!-- somewhere else, using my-modal.vue --> <template> <my-modal> <template #header><!-- using the shorthand for `v-slot` --> <h5>Awesome Interruption!</h5> </template> <template #body> <p>We interrupt your use of our application to let you know that this application is awesome and you should continue using it every day for the rest of your life!</p> </template> <!-- pull in `closeModal` and use it in a button's click handler --> <template #footer="{closeModal}"> <button @click="closeModal"> Take me back to the app so I can be awesome </button> </template> </my-modal> </template>

Composants sans rendu

Et enfin, vous pouvez prendre ce que vous savez sur l'utilisation des slots pour transmettre des fonctionnalités réutilisables et supprimer pratiquement tout le HTML et n'utiliser que les slots. C'est essentiellement ce qu'est un composant sans rendu : un composant qui ne fournit que des fonctionnalités sans aucun code HTML.

Rendre les composants vraiment sans rendu peut être un peu délicat car vous devrez écrire des fonctions de render plutôt que d'utiliser un modèle afin de supprimer le besoin d'un élément racine, mais cela n'est pas toujours nécessaire. Jetons un coup d'œil à un exemple simple qui nous permet d'abord d'utiliser un modèle :

 <template> <transition name="fade" v-bind="$attrs" v-on="$listeners"> <slot></slot> </transition> </template> <style> .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter, .fade-leave-to { opacity: 0; } </style>

Il s'agit d'un exemple étrange de composant sans rendu, car il ne contient même pas de JavaScript. C'est principalement parce que nous créons simplement une version réutilisable préconfigurée d'une fonction intégrée sans rendu : transition .

Oui, Vue a des composants intégrés sans rendu. Cet exemple particulier est tiré d'un article sur les transitions réutilisables par Cristi Jora et montre un moyen simple de créer un composant sans rendu qui peut standardiser les transitions utilisées dans votre application. L'article de Cristi va beaucoup plus en profondeur et montre des variantes plus avancées de transitions réutilisables, je vous recommande donc de le vérifier.

Pour notre autre exemple, nous allons créer un composant qui gère la commutation de ce qui est affiché pendant les différents états d'une promesse : en attente, résolu avec succès et échoué. C'est un modèle courant et bien qu'il ne nécessite pas beaucoup de code, il peut brouiller beaucoup de vos composants si la logique n'est pas retirée pour la réutilisation.

 <!-- promised.vue --> <template> <span> <slot name="rejected" v-if="error" :error="error"></slot> <slot name="resolved" v-else-if="resolved" :data="data"></slot> <slot name="pending" v-else></slot> </span> </template> <script> export default { props: { promise: Promise }, data: () => ({ resolved: false, data: null, error: null }), watch: { promise: { handler (promise) { this.resolved = false this.error = null if (!promise) { this.data = null return } promise.then(data => { this.data = data this.resolved = true }) .catch(err => { this.error = err this.resolved = true }) }, immediate: true } } } </script>

Que se passe-t-il? Tout d'abord, notez que nous recevons un accessoire appelé promise qui est une Promise . Dans la section watch , nous surveillons les modifications apportées à la promesse et lorsqu'elle change (ou immédiatement lors de la création du composant grâce à la propriété immediate ), nous effaçons l'état, then appelons et catch la promesse, mettant à jour l'état lorsqu'il se termine avec succès ou échoue.

Ensuite, dans le modèle, nous affichons un emplacement différent en fonction de l'état. Notez que nous n'avons pas réussi à le garder vraiment sans rendu car nous avions besoin d'un élément racine pour utiliser un modèle. Nous transmettons également data et les error aux étendues d'emplacement concernées.

Et voici un exemple d'utilisation :

 <template> <div> <promised :promise="somePromise"> <template #resolved="{ data }"> Resolved: {{ data }} </template> <template #rejected="{ error }"> Rejected: {{ error }} </template> <template #pending> Working on it... </template> </promised> </div> </template> ...

Nous passons somePromise au composant sans rendu. En attendant qu'il se termine, nous affichons « J'y travaille… » ​​grâce au slot pending . Si cela réussit, nous affichons "Résolu :" et la valeur de résolution. En cas d'échec, nous affichons "Rejeté :" et l'erreur qui a provoqué le rejet. Désormais, nous n'avons plus besoin de suivre l'état de la promesse dans ce composant, car cette partie est extraite dans son propre composant réutilisable.

Alors, que pouvons-nous faire à propos de cette span qui s'enroule autour des emplacements dans promised.vue ? Pour le supprimer, nous devrons supprimer la partie template et ajouter une fonction de render à notre composant :

 render () { if (this.error) { return this.$scopedSlots['rejected']({error: this.error}) } if (this.resolved) { return this.$scopedSlots['resolved']({data: this.data}) } return this.$scopedSlots['pending']() }

Il n'y a rien de trop compliqué ici. Nous utilisons simplement des blocs if pour trouver l'état, puis renvoyons l'emplacement correct (via this.$scopedSlots['SLOTNAME'](...) ) et transmettons les données pertinentes à l'étendue de l'emplacement. Lorsque vous n'utilisez pas de modèle, vous pouvez ignorer l'utilisation de l'extension de fichier .vue en extrayant le JavaScript de la balise de script et en le plaçant simplement dans un fichier .js . Cela devrait vous donner une très légère augmentation des performances lors de la compilation de ces fichiers Vue.

Cet exemple est une version simplifiée et légèrement modifiée de vue-promised, que je recommanderais plutôt d'utiliser l'exemple ci-dessus, car il couvre certains pièges potentiels. Il existe également de nombreux autres excellents exemples de composants sans rendu. Baleada est une bibliothèque complète pleine de composants sans rendu qui fournissent des fonctionnalités utiles comme celle-ci. Il existe également vue-virtual-scroller pour contrôler le rendu de l'élément de liste en fonction de ce qui est visible à l'écran ou PortalVue pour "téléporter" le contenu vers des parties complètement différentes du DOM.

Je suis dehors

Les machines à sous de Vue amènent le développement basé sur les composants à un tout autre niveau, et même si j'ai démontré de nombreuses façons d'utiliser les machines à sous, il en existe d'innombrables autres. À quelle idée géniale pouvez-vous penser? Selon vous, de quelles manières les machines à sous pourraient-elles être améliorées ? Si vous en avez, assurez-vous d'apporter vos idées à l'équipe Vue. Que Dieu vous bénisse et bon codage.