
Le but et les bases
Les slots permettent de passer du contenu d'un parent à un enfant.

Aussi si ! Mais voici un exemple pour que tu comprennes mieux. J'ai ce contenu HTML :
{"language":"text/html","content":"<h2>Ma recette de cuisine</h2>\n<h3>Ingrédients</h3>\n<ul>\n\t<li>...</li>\n</ul>\n<h3>Description</h3>\n<p>...</p>","filename":""}
J'aimerais le mettre dans une card. J'ai déjà conçu cet élément dans un composant que j'ai appelé "card". Passer ce contenu HTML sous forme de props ce n'est quand même pas pratique... Ce serait bien de pouvoir faire la chose suivante :
{"language":"text/html","content":"<card>\n\t<h2>Ma recette de cuisine</h2>\n\t<h3>Ingrédients</h3>\n\t<ul>\n\t\t<li>...</li>\n\t</ul>\n\t<h3>Description</h3>\n\t<p>...</p>\n</card>","filename":""}
Hé bien, tu l'auras deviné : les slots répondent à ce besoin !
On peut passer du contenu HTML à un composant de tel sorte que celui-ci le réutilise via les slots.
Prenons le composant card suivant par exemple :
{"language":"text/html","content":"<template>\n\t<div class=\"card\">\n\t</div>\n</template>\n\n<style scoped>\n\t.card{\n\t\tborder:1px solid black;\n\t\tpadding: 10px 15px;\n\t}\n</style>","filename":"Card.vue"}
Pour que ce composant réutilise le contenu qu'on lui passe, on va utiliser la balise <slot> comme suit :
{"language":"text/html","content":"<template>\n\t<div class=\"card\">\n\t\t<slot></slot>\n\t</div>\n</template>\n\n<style scoped>\n\t.card{\n\t\tborder:1px solid black;\n\t\tpadding: 10px 15px;\n\t}\n</style>","filename":"Card.vue"}
Et prenons le code suivant :
{"language":"text/html","content":"<card>\n\t<h2>Ma recette de cuisine</h2>\n\t<h3>Ingrédients</h3>\n\t<ul>\n\t\t<li>...</li>\n\t</ul>\n\t<h3>Description</h3>\n\t<p>...</p>\n</card>","filename":"App.vue"}
Voici le résultat :

On voit bien que notre code HTML correspondant à la recette a été intégré au sein du composant, à la place de <slot> !

Les slots nommés
Nous allons maintenant répondre à une nouvelle problématique. Imaginons que notre composant card soit maintenant le suivant :
{"language":"text/html","content":"<template>\n\t<div class=\"card\">\n\t\t<h2 class=\"card__title\"></h2>\n\t\t<div class=\"card__content\"></div>\n\t</div>\n</template>\n\n<style scoped>\n\t.card{\n\t\tborder:1px solid black;\n\t\tpadding: 10px 15px;\n\t}\n</style>","filename":"Card.vue"}
Nous voyons que nous avons maintenant un emplacement pour un titre et un emplacement pour le contenu de la card.

Tout à fait. Si on utilise encore <slot> </slot> et qu'on le met dans la div card__content par exemple, le titre va toujours rester vide, et inversement.
Pour palier à ce problème, on peut utiliser les slots nommés. Cela nous permet d'utiliser plusieurs slots au sein d'un même composant. Pour les utiliser il nous suffit de rajouter l'attribut name="" sur les balises slots et de choisir des noms différents.
Notre composant card modifié ressemblera donc à :
{"language":"text/html","content":"<template>\n\t<div class=\"card\">\n\t\t<h2 class=\"card__title\">\n\t\t\t<slot name=\"title\"></slot>\n\t\t</h2>\n\t\t<div class=\"card__content\">\n\t\t\t<slot name=\"content\"></slot>\n\t\t</div>\n\t</div>\n</template>\n\n<style scoped>\n\t.card{\n\t\tborder:1px solid black;\n\t\tpadding: 10px 15px;\n\t}\n</style>","filename":"Card.vue"}

On va utiliser une nouvelle directive : v-slot. Elle a une syntaxe un peu différente des directives vues précédemment dans le cours. Je te laisse regarder par toi-même :
{"language":"text/html","content":"<card>\n <template v-slot:title>Ma recette de cuisine</template>\n <template v-slot:content>\n <h3>Ingrédients</h3>\n <ul>\n <li>...</li>\n </ul>\n <h3>Description</h3>\n <p>...</p>\n </template>\n</card>","filename":"App.vue"}

La balise template permet juste d'englober, elle ne rajoutera aucune balise (div, p...) dans le rendu. C'est très pratique ;).
Une autre information qui a son importance : le slot par défaut qui s'utilise avec <slot> </slot> a aussi un nom : default. Ainsi, on peut dans un composant parent faire <template v-slot:default></template> ;)

Bon, je dois t'avouer qu'on a encore une problématique à résoudre. Notamment si on veut utiliser les slots au sein de notre projet pour pouvoir utiliser notre slider. Pour bien comprendre le souci, on va devoir réfléchir à la portée des slots. Leur scope.
Agir sur le scope
Ce qu'il faut savoir, c'est que chaque composant a accès par défaut à son propre contenu dans le cadre des slots.

Tu vas vite le voir...
Dans le cadre de notre projet on veut que les photos s'affichent dans notre slider. Le but des composants est que ceux-si soient réutilisables. Ainsi, hors de question de mettre directement dans le composant Slider une quelconque architecture HTML propre à nos photos. Non, il est important que chaque slide du Slider soit indépendante du composant et qu'on puisse ainsi les designer comme on le souhaite depuis le composant parent. Cela implique d'utiliser un slot au sein de notre composant Slider. J'en profite aussi pour passer les slides sous forme de props. Voici donc le résultat :
{"language":"text/html","content":"<template>\n <div>\n <button @click=\"slide(-1)\">Aller à gauche</button>\n <button @click=\"slide(1)\">Aller à droite</button>\n <template v-for=\"(slide, key) in slides\" :key=\"key\">\n <div v-if=\"index === key\">\n <slot></slot>\n </div>\n\n </template>\n </div>\n</template>\n\n<script>\n export default {\n props:['slides'],\n data() {\n return {\n index:0\n }\n },\n methods:{\n slide(operation) {\n // Car % n'est pas le modulo mathématique en JS\n // on doit faire un code un peu compliqué\n // pour gérer les nombres négatifs\n this.index = (((this.index + operation)%this.slides.length)+this.slides.length)%this.slides.length\n }\n },\n }\n</script>","filename":"Slider.vue"}
Du coup, on va modifier notre App.vue en conséquence :
{"language":"text/html","content":"<template>\n\t<!-- Le composant photo actuellement -->\n <photo \n v-for=\"photoLoop in photos\" \n :key=\"photoLoop\" \n v-bind=\"photoLoop\"\n ></photo>\n\t\n\t<!-- Le slider -->\n <slider :slides=\"photos\">\n </slider>\n</template>\n\n<script>\nimport Photo from './components/Photo.vue'\nimport Slider from './components/Slider.vue'\nconst API_URL = 'https://api.nasa.gov/planetary/apod?start_date=2021-02-01&end_date=2021-02-17&api_key=DEMO_KEY'\nexport default {\n name: 'App',\n components: {\n Photo, Slider\n },\n\n data() {\n return {\n photos: [],\n }\n },\n created() {\n fetch(API_URL)\n .then(result => result.json())\n .then(result => {this.photos = result})\n }\n}\n</script>\n","filename":"App.vue"}

Tout à fait. Je l'ai laissé pour que tu comprennes bien. Ce qu'on aimerait faire c'est passer sous forme de slot le composant Photo à notre slider.

{"language":"text/html","content":"<template>\n\t<!-- Le slider -->\n <slider :slides=\"photos\">\n\t\t<photo \n v-for=\"photoLoop in photos\" \n :key=\"photoLoop\" \n v-bind=\"photoLoop\"\n \t></photo>\n </slider>\n</template>\n\n<script>\nimport Photo from './components/Photo.vue'\nimport Slider from './components/Slider.vue'\nconst API_URL = 'https://api.nasa.gov/planetary/apod?start_date=2021-02-01&end_date=2021-02-17&api_key=DEMO_KEY'\nexport default {\n name: 'App',\n components: {\n Photo, Slider\n },\n\n data() {\n return {\n photos: [],\n }\n },\n created() {\n fetch(API_URL)\n .then(result => result.json())\n .then(result => {this.photos = result})\n }\n}\n</script>\n","filename":"App.vue"}
As-tu testé ce code ?

Tout à fait. Je rappelle que le v-for pour chaque photo est maintenant au sein du composant Slider vu qu'on a une photo par slide !

{"language":"text/html","content":"<template>\n\t<!-- Le slider -->\n <slider :slides=\"photos\">\n\t\t<photo></photo>\n </slider>\n</template>\n\n<script>\nimport Photo from './components/Photo.vue'\nimport Slider from './components/Slider.vue'\nconst API_URL = 'https://api.nasa.gov/planetary/apod?start_date=2021-02-01&end_date=2021-02-17&api_key=DEMO_KEY'\nexport default {\n name: 'App',\n components: {\n Photo, Slider\n },\n\n data() {\n return {\n photos: [],\n }\n },\n created() {\n fetch(API_URL)\n .then(result => result.json())\n .then(result => {this.photos = result})\n }\n}\n</script>\n","filename":"App.vue"}
Ah mais attends... Comment je passe au composant Photo les props maintenant ?
Bingo ! Tu en es arrivé à la conclusion que je voulais : on a un souci avec l'utilisation de slot. Ce souci vient du fait qu'on a besoin de données du composant enfant (Slider) dans le composant parent (App). On a en effet besoin de la photo qui vient de la boucle suivante dans notre composant App :
{"language":"text/html","content":"<template v-for=\"(slide, key) in slides\" :key=\"key\">\n\t<div v-if=\"index === key\">\n\t\t<!-- Ici j'ai accès à une photo. C'est le paramètre \"slide\" -->\n\t\t<slot></slot>\n\t</div>\n</template>","filename":"Slider.vue"}
Et c'est ce que je te disais :
Ce qu'il faut savoir, c'est que chaque composant a accès par défaut à son propre contenu dans le cadre des slots.

Exactement. Heureusement, notre framework préféré a pensé à ce cas d'utilisation. Un enfant peut laisser accès à plusieurs de ses données à son parent grâce à ce qu'on appelle les scoped slots. Tout repose sur notre directive préférée qui a plein de ressources. Une idée ?

Tout à fait. Dans le composant enfant (ici Slider) on va v-bind les données qu'on veut passer au parent directement sur le slot concerné. Ainsi, on a :
{"language":"text/html","content":"<template>\n <div>\n <button @click=\"slide(-1)\">Aller à gauche</button>\n <button @click=\"slide(1)\">Aller à droite</button>\n <template v-for=\"(slide, key) in slides\" :key=\"key\">\n <div v-if=\"index === key\">\n <slot :slide=\"slide\"></slot>\n\t\t\t\t<!-- On peut v-bind autant de données qu'on veut.\n\t\t\t\t\tOn aurait pu donc aussi passer d'autres données.\n \t\t\t\t\tPar exemple : \n\t\t\t\t-->\n\t\t\t\t<!--\n\t\t\t\t<slot :slide=\"slide\" :key=\"key\" :indexCourant=\"index\"></slot>\n\t\t\t\t-->\n </div>\n\n </template>\n </div>\n</template>\n\n<script>\n export default {\n props:['slides'],\n data() {\n return {\n index:0\n }\n },\n methods:{\n slide(operation) {\n // Car % n'est pas le modulo mathématique en JS\n // on doit faire un code un peu compliqué\n // pour gérer les nombres négatifs\n this.index = (((this.index + operation)%this.slides.length)+this.slides.length)%this.slides.length\n }\n },\n }\n</script>","filename":"Slider.vue"}
Ces données sont maintenant récupérables au sein du composant parent grâce au template correspondant au slot. Dans le cadre de notre slider vu qu'on n'a qu'un seul slot, le template correspondant est default. On va alors modifier un peu la syntaxe qu'on a vue :
{"language":"text/html","content":"<template v-slot:default>\n</template","filename":""}
En rajoutant une valeur à notre v-bind qui sera un objet contenant les attributs passés par l'enfant au sein de ce slot. En général on l'appelle "slotProps" :
{"language":"text/html","content":"<template v-slot:default=\"slotProps\">\n</template","filename":""}
Et maintenant, on peut accéder aux données en faisant slotsProps.donnee. Dans notre cas :
{"language":"text/html","content":"<template>\n <slider :slides=\"photos\">\n <template v-slot:default=\"slotProps\">\n <photo \n v-bind=\"slotProps.slide\"\n ></photo>\n\t\t\t<!-- Si on avait passé les 2 autres données key et index on aurait pu y accéder : \n\t\t\t-->\n\t\t\t<!--\n\t\t\t{{ slotProps.key }}\n\t\t\t{{ slotProps.indexCourant }}\n\t\t\t-->\n\t\t\t\n\t\t</template>\n </slider>\n</template>\n\n<script>\nimport Photo from './components/Photo.vue'\nimport Slider from './components/Slider.vue'\nconst API_URL = 'https://api.nasa.gov/planetary/apod?start_date=2021-02-01&end_date=2021-02-17&api_key=DEMO_KEY'\nexport default {\n name: 'App',\n components: {\n Photo, Slider\n },\n\n data() {\n return {\n photos: [],\n }\n },\n created() {\n fetch(API_URL)\n .then(result => result.json())\n .then(result => {this.photos = result})\n }\n}\n</script>\n","filename":"App.vue"}

Oui, je sais que c'est un peu compliqué. Cependant, c'est vraiment quelque chose à prendre en main. Je t'encourage à lire, lire, relire... Et à poser tes questions si tu en as évidemment. Car on va encore rajouter quelques informations.

Oui mais pour ton bien, car on va améliorer la lisibilité du code :p. Actuellement le code est peu compréhensible : on passe une slotProps.slide à notre composant Photo, on comprend pas trop ce qu'il se passe si on met le nez sur le code la première fois. "Qu'est-ce que slotProps.slide ?" va se demander une personne qui voit le code la première fois (ou toi si tu n'as pas revu ton code depuis des mois èé !).
Nous pouvons donc rendre le code plus lisible en utilisant le "Destructuring assignment" offert par ES6. On peut donc transformer le nom des données passées par notre enfant au moment où on le récupère dans le parent. Ce serait sympa de modifier la donnée qui a pour nom "slide" (et qui contient les données d'une photo dans notre cas) par le nom "photo". Voici comment faire :
{"language":"text/html","content":"<template>\n <slider :slides=\"photos\">\n <template v-slot:default=\"{slide : photo}\">\n <photo \n v-bind=\"photo\"\n ></photo>\n </template>\n \n </slider>\n</template>\n\n<script>\nimport Photo from './components/Photo.vue'\nimport Slider from './components/Slider.vue'\nconst API_URL = 'https://api.nasa.gov/planetary/apod?start_date=2021-02-01&end_date=2021-02-17&api_key=DEMO_KEY'\nexport default {\n name: 'App',\n components: {\n Photo, Slider\n },\n\n data() {\n return {\n photos: [],\n }\n },\n created() {\n fetch(API_URL)\n .then(result => result.json())\n .then(result => {this.photos = result})\n }\n}\n</script>\n","filename":"App.vue"}

Syntaxe raccourcie
Allez un dernier point rapidement. Peut être que tu t'en doutais, Vue.JS nous offre une syntaxe raccourcie pour la directive v-slot au même titre que v-bind devient " : " ou que v-on devient "@". On utilisera ici le dièse "#". v-slot:default devient #default ;).
Voici donc notre code terminé avec une légère modification en utilisant "key" pour que, peut être, cela te permette de mieux comprendre quand tu reliras le code :
{"language":"text/html","content":"<template>\n <slider :slides=\"photos\">\n <template #default=\"{slide : photo, key : index}\">\n\t\t\t<h2>Photo {{ index+1 }}/{{ photos.length }}</h2>\n <photo \n v-bind=\"photo\"\n ></photo>\n </template>\n \n </slider>\n</template>\n\n<script>\nimport Photo from './components/Photo.vue'\nimport Slider from './components/Slider.vue'\nconst API_URL = 'https://api.nasa.gov/planetary/apod?start_date=2021-02-01&end_date=2021-02-17&api_key=DEMO_KEY'\nexport default {\n name: 'App',\n components: {\n Photo, Slider\n },\n\n data() {\n return {\n photos: [],\n }\n },\n created() {\n fetch(API_URL)\n .then(result => result.json())\n .then(result => {this.photos = result})\n }\n}\n</script>\n","filename":"App.vue"}
{"language":"text/html","content":"<template>\n <div>\n <button @click=\"slide(-1)\">Aller à gauche</button>\n <button @click=\"slide(1)\">Aller à droite</button>\n <template v-for=\"(slide, key) in slides\" :key=\"key\">\n <div v-if=\"index === key\">\n <slot :slide=\"slide\" :key=\"key\"></slot>\n </div>\n\n </template>\n </div>\n</template>\n\n<script>\n export default {\n props:['slides'],\n data() {\n return {\n index:0\n }\n },\n methods:{\n slide(operation) {\n this.index = (((this.index + operation)%this.slides.length)+this.slides.length)%this.slides.length\n }\n },\n }\n</script>","filename":"Slider.vue"}

Oui il faut bien comprendre cette partie. C'est un cas d'utilisation très courant ! Les slots sont au coeur de la logique des composants. N'hésite pas à prendre un peu de temps sur ce chapitre si tu en ressens le besoin. Tu peux aussi te reposer un peu en embellissant notre application qui pour le moment est un peu... "moche", avouons-le !

Ça marche ! Quand tu te sens prêt on se revoit pour voir la communication enfant -> parent cette fois-ci pour rajouter une modale sur notre application ! J'ai terminé cette partie