Commentaires
Pour appréhender les props, il faut bien comprendre une notion simple concernant les composants. Pour ce, discutons généalogie rapidement.
Quand on appelle un composant dans un composant, il faut voir ça comme une belle famille. Le composant au sein du composant sera enfant et l'autre le parent.
Un exemple très rapide :

{"language":"text/html","content":"<!-- Fichier App.vue par exemple -->\n<onglets>\n\t<article></article>\n\t<article></article>\n</onglets>","filename":"App.vue"}

On a ici le composant App qui est le parent de "Onglets". Et "Onglets" qui est le parent de 3 composants "Article". Donc "Article" est le petit-fils de "App".

Compris ?

Arthur, l'apprenti développeurNotion simple comme tu as dit, tout compris !
Parfait.
Pour revenir à notre problème, on doit passer dans notre cas des données de notre composant App vers notre composant Photo. C'est donc un passage de données de parent vers enfant. Et pour ce, comme dit avant, la stratégie est l'utilisation des props.

L'utilisation des props côté parent


Dans un premier temps on va les utiliser côté parent. C'est à dire passer les données aux enfants. La deuxième partie traitera de comment s'opère la récupération côté enfant.

Donc, pour passer les éléments qu'on veut on va tout simplement utiliser des attributs.

Arthur, l'apprenti développeurDes attributs ?
Oui, tout simplement. Un attribut qui portera le nom de la donnée qu'on utilisera dans notre composant. Dans notre cas, on utilise dans notre composant photo.url, photo.truc, donc on doit donner comme attribut "photo". Et comme valeur... La photo.

{"language":"text/html","content":"<template>\n <photo v-for=\"photo in photos\" :key=\"photo\" photo=\"photo\"></photo>\n</template>","filename":"App.vue"}

Arthur, l'apprenti développeurJe suis un peu perdu avec ces "photo" partout...
Ok, alors voici le même code avec des noms de variable modifiés pour que tu comprennes bien :

{"language":"text/html","content":"<template>\n <photo v-for=\"photoLoop in photos\" :key=\"photoLoop\" photo=\"photoLoop\"></photo>\n</template>","filename":"App.vue"}

Arthur, l'apprenti développeurOk je comprends mieux ! C'est tout ?
C'est tout. Enfin, presque tout. Là, on a utilise un attribut HTML classique, donc la valeur "photoLoop" passée en paramètre sera de type string. Ce ne sera pas l'objet photo. On doit donc utiliser v-bind.

{"language":"text/html","content":"<template>\n <photo v-for=\"photo in photos\" :key=\"photo\" :photo=\"photo\"></photo>\n</template>","filename":"App.vue"}

Et voilà :)

La récupération côté enfant


Bon alors là si tu testes tu auras toujours le même souci de photo undefined. En effet, on a passé la donnée à l'enfant mais ce dernier ne l'a pas récupérée !
Pour utiliser des données dans le template il faut que celle-ci soit renseignée dans le script. Cependant ici on ne la renseignera pas dans data() { } mais dans un élément nommé props.

Props sous forme de tableau


La façon la plus simple et la plus rapide est d'utiliser "props" comme un tableau, comme suit :

{"language":"text/html","content":"<template>\n <photo v-for=\"photoLoop in photos\" :key=\"photoLoop\" :photo=\"photoLoop\"></photo>\n</template>\n\n<script>\nimport Photo from './components/Photo.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 ...\n}\n</script>\n","filename":"App.vue"}

{"language":"text/html","content":"<template>\n <figure>\n <img :src=\"photo.url\" :alt=\"photo.title + 'prise par' +photo.copyright\" />\n <figcaption>\n <h2>{{ photo.title }} prise le {{ formattedDate }}</h2>\n <p>{{ photo.explanation }}</p>\n </figcaption>\n </figure>\n</template>\n\n<script>\n export default {\n props:['photo'], // Mon props sous forme de tableau\n computed: {\n formattedDate() {\n let date = new Date(this.photo.date) // Ici j'utilise this.photo\n let day = Number(date.getDate()) >= 10 ? date.getDate() : '0'+date.getDate()\n return `${day}/${date.getMonth()+1}/${date.getFullYear()}`\n }\n }\n }\n</script>\n\n<style>\n\n</style>","filename":"Photo.vue"}

Tu auras remarqué que j'utilise non plus photo.date dans ma CP mais this.photo.date : un élément dans le props s'utilise comme un élément dans data(), il faut "this" pour l'utiliser !
Est-ce que c'est compris ?

Arthur, l'apprenti développeurC'est compris !

Les props sous forme d'objet


On peut donner une force plus contraignante à nos props en les renseignant sous forme d'objet. Ainsi, on pourra forcer le type de la props ou encore lui donner une valeur par défaut.
Voyons pour commencer comment préciser le type d'une prop. Rien de plus simple :

{"language":"text/javascript","content":"export default {\n props:{\n photo:Object // J'impose que photo doit être un Objet\n },\n computed: {\n formattedDate() {\n let date = new Date(this.photo.date)\n let day = Number(date.getDate()) >= 10 ? date.getDate() : '0'+date.getDate()\n return `${day}/${date.getMonth()+1}/${date.getFullYear()}`\n }\n }\n}","filename":""}

On peut aussi préciser une valeur par défaut. Pour ça, il faut transformer un peu la syntaxe. On ne va plus faire prop:Type, mais prop:{type:Type, default:ValeurParDefaut}. Dans le cas de données passées en props de type Array ou Object, on doit passer une valeur par défaut sous forme de fonction :

{"language":"text/javascript","content":"export default {\n props:{\n photo: {\n type:Object,\n default: function() {\n return {\n url:'https://image-par-defaut.png',\n title:'Cette image est par défaut',\n copyright:'Antoine',\n date: Date.now(),\n explanation: 'Cette image est une image par défaut'\n }\n }\n }\n },\n computed: {\n formattedDate() {\n let date = new Date(this.photo.date)\n let day = Number(date.getDate()) >= 10 ? date.getDate() : '0'+date.getDate()\n return `${day}/${date.getMonth()+1}/${date.getFullYear()}`\n }\n }\n}","filename":""}


Enfin, on peut aussi préciser si la prop est obligatoire ou non.

{"language":"text/javascript","content":"export default {\n props:{\n photo: {\n type:Object,\n default: function() {\n return {\n url:'https://image-par-defaut.png',\n title:'Cette image est par défaut',\n copyright:'Antoine',\n date: Date.now(),\n explanation: 'Cette image est une image par défaut'\n }\n },\n required:true\n }\n },\n computed: {\n formattedDate() {\n let date = new Date(this.photo.date)\n let day = Number(date.getDate()) >= 10 ? date.getDate() : '0'+date.getDate()\n return `${day}/${date.getMonth()+1}/${date.getFullYear()}`\n }\n }\n}","filename":""}


Est-ce que c'est compris tout ça ?

Arthur, l'apprenti développeurHum... Pas sûr. On peut voir d'autres exemples ?
C'est prévu ! Pour ça, voyons justement les bonnes pratiques pour améliorer notre code.

Bonnes pratiques


En réalité, on va rarement passer tout l'objet "photo" à notre composant "photo". On préfèrera passer donnée par donnée ce qui compose notre photo (le copyright, l'explanation, l'url...).

Arthur, l'apprenti développeurPour quelle raison ?
Je dirais que c'est une question de "logique". Le composant Photo "représente" une photo, c'est donc bizarre de lui passer une prop "photo" et d'utiliser toujours photo.url, photo.quelquechose... Ca pourrait sous entendre qu'on a une boucle dans notre composant. Du coup, pour écarter tout doute et rester cohérent, on passe directement les données de la photo.
On aura donc le code suivant du côté de App.vue :

{"language":"text/html","content":"<template>\n <photo v-for=\"photoLoop in photos\" \n :key=\"photoLoop\" \n :url=\"photoLoop.url\" \n :title=\"photoLoop.title\"\n :explanation=\"photoLoop.explanation\"\n :copyright=\"photoLoop.copyright\"\n :date=\"photoLoop.date\"\n ></photo>\n</template>\n\n<script>\nimport Photo from './components/Photo.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\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"}

Avant que tu ne poses de questions, simple précision : Vue recommande d'écrire les attributs comme ci-dessus (avec un saut de ligne après chaque) pour gagner en clarté.

Arthur, l'apprenti développeurOk c'est noté ! Petite question : ça implique qu'on change tout notre code du côté de Photo.vue n'est-ce pas ?
En effet, et je te propose d'essayer et de me montrer le résultat.

Arthur, l'apprenti développeurVoilà ce à quoi j'aboutis... Je ne suis pas sûr du tout. J'ai mis quelques valeurs par défaut mais ne suis sûr de rien.

{"language":"text/html","content":"<template>\n <figure>\n <img :src=\"url\" :alt=\"title + 'prise par' +copyright\" />\n <figcaption>\n <h2>{{ title }} prise le {{ formattedDate }} par {{ copyright }}</h2>\n <p>{{ explanation }}</p>\n </figcaption>\n </figure>\n</template>\n\n<script>\nexport default {\n props:{\n url: {\n type:String, \n required:true,\n default:'http://uneimagepardefaut.png'\n },\n title: {\n type:String,\n required:true,\n },\n date: {\n type:Date,\n required:true,\n default:Date.now()\n },\n explanation: {\n type:String,\n required:true,\n },\n copyright: {\n type:String,\n required:true,\n }\n },\n computed: {\n formattedDate() {\n let date = new Date(this.date)\n let day = Number(date.getDate()) >= 10 ? date.getDate() : '0'+date.getDate()\n return `${day}/${date.getMonth()+1}/${date.getFullYear()}`\n }\n }\n}\n</script>","filename":"Photo.vue"}


Hé bien ce n'est pas mal du tout ! Simplement quelques petites erreurs !
Premièrement, n'oublie que la date qu'on passe est sous format String, pas sous format Date (c'est pour ça que dans la CP on est obligé de faire new Date()). Deux solutions s'offrent à toi : changer le type en String, ou bien passer non pas :date="photoLoop.date" mais :date="new Date(photoLoop.date)"
Dernièrement, si on regarde ce que nous retourne l'API, on s'aperçoit qu'il n'y a pas toujours de copyright de fourni... On ne peut donc pas rendre cette prop obligatoire. Et on va lui mettre un copyright par défaut.
Voici donc la correction que je te propose :

{"language":"text/html","content":"<template>\n <figure>\n <img :src=\"url\" :alt=\"title + 'prise par' +copyright\" />\n <figcaption>\n <h2>{{ title }} prise le {{ formattedDate }} par {{ copyright }}</h2>\n <p>{{ explanation }}</p>\n </figcaption>\n </figure>\n</template>\n\n<script>\nexport default {\n props:{\n url: {\n type:String, \n required:true,\n default:'http://uneimagepardefaut.png'\n },\n title: {\n type:String,\n required:true,\n },\n date: {\n type:Date,\n required:true,\n default:Date.now()\n },\n explanation: {\n type:String,\n required:true,\n },\n copyright: {\n type:String,\n required:false,\n default:'un inconnu'\n }\n },\n computed: {\n formattedDate() {\n let date = this.date\n let day = Number(date.getDate()) >= 10 ? date.getDate() : '0'+date.getDate()\n return `${day}/${date.getMonth()}/${date.getFullYear()}`\n }\n }\n}\n</script>","filename":"Photo.vue"}

{"language":"text/html","content":"<template>\n <photo \n v-for=\"photoLoop in photos\" \n :key=\"photoLoop\" \n :url=\"photoLoop.url\" \n :title=\"photoLoop.title\"\n :explanation=\"photoLoop.explanation\"\n :copyright=\"photoLoop.copyright\"\n :date=\"new Date(photoLoop.date)\"\n ></photo>\n</template>\n\n<script>\nimport Photo from './components/Photo.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\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"}


Arthur, l'apprenti développeurC'est compris ! J'étais pas loin du but !
Oui. Et tiens, une petite astuce. Depuis Vue.JS 3, on a une syntaxe raccourcie pour passer tous nos props du parent vers l'enfant.
On peut passer de ça :

{"language":"text/html","content":"<template>\n <photo \n v-for=\"photoLoop in photos\" \n :key=\"photoLoop\" \n :url=\"photoLoop.url\" \n :title=\"photoLoop.title\"\n :explanation=\"photoLoop.explanation\"\n :copyright=\"photoLoop.copyright\"\n :date=\"new Date(photoLoop.date)\"\n ></photo>\n</template>","filename":"App.vue"}

À ça :

{"language":"text/html","content":"<template>\n <photo \n v-for=\"photoLoop in photos\" \n :key=\"photoLoop\" \n v-bind=\"photoLoop\"\n ></photo>\n</template>\n\n<script>\nimport Photo from './components/Photo.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\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"}

Au détail près que maintenant la prop date n'est plus castée en date, on doit donc modifier légèrement notre Photo.vue pour indiquer que date est de type String et changer notre CP pour qu'elle redevienne comme avant.
En fait ici on utilise la deuxième syntaxe de v-bind. Il existe deux syntaxes équivalentes pour v-bind :

{"language":"text/html","content":"<balise v-bind:key=\"value\" v-bind:key2=\"value2\"></balise>\n<!-- Équivalent à : -->\n<balise v-bind=\"{key:value, key2:value2}\"></balise>","filename":""}

Arthur, l'apprenti développeurAh donc en fait en utilisant v-bind="photo" on passe toutes les données d'une photo, pas que ce qui nous intéresse ! Donc aussi hdurl ou media_type par exemple. Qu'est-ce qu'ils deviennent ?
Tu as tout à fait raison, on les passe aussi. Ce n'est pas gênant mais il est intéressant de savoir ce qu'ils deviennent. La réponse : ils deviennent des attributs non props (en Anglais non-props attributes). En fait ils sont passés comme des attributs classiques en HTML. On peut facilement le remarquer en inspectant le code HTML généré :


Il n'y a rien de particulier à savoir concernant les non-props attributes si ce n'est qu'on peut y accéder depuis l'enfant grâce à $attrs. Tu peux essayer de faire un {{ $attrs }} dans Photo.vue pour voir le résultat ;). En réalité, on verra dans un prochain chapitre que ça peut nous être utile dans un cas particulier.

Arthur, l'apprenti développeurHâte de voir ça. Mais en attendant ce prochain chapitre, qu'allons-nous voir maintenant ?
Au début du cours on avait vu la réalisation d'un slider. Je te propose qu'on le modifie un peu pour qu'on puisse l'appliquer à notre projet pour chaque photo !

Arthur, l'apprenti développeurClassee ! Je te suis !
Alors direction l'apprentissage des slots ! J'ai terminé cette partie
Demander de l'assistance