Cours 06 - Vue.js - Réactivité et Composants
Table des matières
Objectifs
- Connaître la notion de réactivité sous Vue.js
- Créer des composants sous Vue.js
- Être en mesure de faire communiquer les composants entre eux.
Déroulement
- Introduction au cours
- Les concepts
- La réactivité (ref et computed)
- Les composants (props)
- Atelier 06 - Réactivité et Composants
- Exercice 06 - Utilisation de Vue.js
- Conclusion
Les concepts
État réactif (ref)
Avec la Composition API, la méthode recommandée pour déclarer l'état réactif consiste à utiliser la fonction ref()
Documentation officielle
Propriétés Calculées (computed)
Les propriétés calculées sont des valeurs dérivées d'états ou de props. Elles se recalculent automatiquement quand les dépendances changent. C'est particulièrement utile pour les calculs qui doivent être mis à jour en réponse à des changements d'état.
Documentation officielle
Exemple:
<script setup lang="ts">
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
</script>
<template>
<div>
<h1>{{ fullName }}</h1>
<p>Prénom : {{ firstName }}</p>
<p>Nom : {{ lastName }}</p>
</div>
</template>
Explication:
- Nous importons les fonctions ref et computed depuis vue.
- Nous définissons deux variables réactives firstName et lastName avec la fonction ref.
- Nous définissons une valeur computed fullName qui utilise la fonction computed.
- La fonction computed retourne une fonction qui calcule la valeur complète du nom en concaténant les valeurs de firstName et lastName.
- Le template utilise les valeurs de firstName, lastName et fullName pour le rendu.
Avantages de l'utilisation d'une valeur computed avec l'API de composition :
- Code plus propre et plus facile à lire
- Meilleure réutilisabilité du code
- Amélioration de la performance car la valeur n'est calculée que lorsque cela est nécessaire
Les composants
Les composants nous permettent de fractionner l'UI en morceaux indépendants et réutilisables, sur lesquels nous pouvons réfléchir de manière isolée. Il est courant pour une application d'être organisée en un arbre de composants imbriqués.
Documentation officielle
Props
Les props sont des options que vous pouvez passer à vos composants Vue. Ils permettent aux composants parents de passer des données aux composants enfants. C'est le moyen principal de faire passer des informations entre composants dans Vue.js.
Composant d'en-tête (Header.vue):
<script setup lang="ts">
defineProps<{
logo: string
title: string
}>()
</script>
<template>
<header>
<img :src="logo" alt="Logo">
<h1>{{ title }}</h1>
</header>
</template>
Page parente (App.vue):
<script setup lang="ts">
import Header from './components/Header.vue'
</script>
<template>
<div>
<Header logo="/logo.png" title="Ma page d'accueil" />
<p>Ceci est le contenu de la page d'accueil.</p>
</div>
</template>
Avantages de l'utilisation de l'API de composition pour les accessoires :
- Syntaxe plus concise et plus propre
- Meilleure réutilisabilité des composants
- Plus facile à tester
Liens utiles
- Site officiel de Vue.js - Guide d'introduction à Vue.js
- Documentation sur la gestion des événements
- Documentation sur Computed
- Documentation sur les props
Atelier 06 - Vue.js - Réactivité et Composants
Prérequis
- À partir de l'atelier 05 fonctionnel du cours précédent
- On va utiliser une librairie de gestion de date/temps nommée luxon: https://moment.github.io/luxon/#/
- Pour débuter l'atelier:
- Effacer le dossier node_modules et dist du dossier de l'atelier 05 (atel05)
- Faite une copie du dossier atel05 et renommez-le atel06
- Dans le terminal, on installe les dépendances et on lance le serveur:
npm install npm run dev
Partie 01 - Typescript et date
Timeline.vue
Dans Timeline.vue on va mieux typer nos données.
-
Dans script après le import on va se crer un nouveau type:
type Period = "Aujourd'hui" | "Cette semaine" | "Ce mois"
la barre | signifie OU
-
La constante de periods, va devenir:
const periods: Period[] = ["Aujourd'hui", "Cette semaine", "Ce mois"]
-
Faire un test en ajoutant un string dedans. Exemple:
const periods: Period[] = ["A", "Aujourd'hui", "Cette semaine", "Ce mois"]
-
Ça devrait afficher une erreur!
-
Il faut ensuite changer le type dans la fonction pour refléter nos changements:
function selectPeriod(period: Period) {
-
On peut remarquer de la redondance dans le code au niveau de ces deux lignes
type Period = "Aujourd'hui" | "Cette semaine" | "Ce mois" const periods: Period[] = ["Aujourd'hui", "Cette semaine", "Ce mois"]
-
On va corriger ça en modifiant comme suit:
const periods = ["Aujourd'hui", "Cette semaine", "Ce mois"] as const type Period = typeof periods[number]
-
On va installer une librairie de gestion de date/temps nommée luxon:
-
Dans un terminal, on va installer la librarie avec la commande:
npm install luxon@2.3.1 @types/luxon@2.3.1
@types/luxon@2.3.1 => va installer les types pour typescript!
posts.ts
-
Créer un fichier à la racine du dossier src nommé posts.ts pour tester la librairie
-
Dans ce fichier, on fait l'importation de la librairie:
import { DateTime } from "luxon"
-
Ensuite on va se créer une interface de Post qui va contenir l'information d'un post:
export interface Post { id: string title: string created: string }
created: string pas au format DateTime parce qu'on pense que ça pourrait venir d'un api!
-
Ensuite une constante qui représente le format pour aujourd'hui
export const today: Post = { id: "1", title: "Aujourd'hui", created: DateTime.now().toISO() }
created: DateTime.now().toISO() => va utiliser le format de luxon converti en string grâce à toISO
-
On fait la même chose pour thisWeek et thisMonth
export const thisWeek: Post = { id: "2", title: "Cette semaine", created: DateTime.now().minus({ days : 5 }).toISO() } export const thisMonth: Post = { id: "3", title: "Ce mois", created: DateTime.now().minus({ weeks : 3 }).toISO() }
.minus({ weeks : 3 }) => permet de revenir 3 semaines auparavant
Timeline.vue
-
Il faut revenir dans Timeline.vue dans la section script pour importer nos posts écrire après l'importation de ref:
import { type Post, today, thisWeek, thisMonth } from "../posts"
-
pour importer la librairie DateTime de luxon:
import { DateTime } from "luxon"
-
Pour utiliser nos posts, on va créer une constante comme suit:
- IMPORTANT Mettez cette constante APRÈS la variable let selectedPeriod = ref("Aujourd'hui")
const posts: Post[] = [ today, thisWeek, thisMonth ]
- IMPORTANT Mettez cette constante APRÈS la variable let selectedPeriod = ref("Aujourd'hui")
-
Dans la section template, on va refaire une boucle pour boucler dans les posts:
- IMPORTANT Remplacer tous le code HTML pour que ce soit plus beau
<div class="btn-group" role="group"> <button type="button" class="btn btn-primary" v-for="period of periods" v-bind:key="period" v-on:click="selectPeriod(period)" v-bind:class="{ 'btn-success': period === selectedPeriod}" > {{ period }} </button> </div> <h2>{{ selectedPeriod }}</h2> <ul> <li v-for="post of posts" :key="post.id"> <span>{{ post.title }}</span> | <span>{{ post.created }}</span> </li> </ul>
post.id permet d'aller chercher l'info de l'ID unique d'un post!
- IMPORTANT Remplacer tous le code HTML pour que ce soit plus beau
-
Aller voir dans le navigateur ça devrait boucler!
-
Le format de la date n'est pas super beau, on va améliorer ça!
-
On ajoute ces infos à la constante de posts:
const posts = [today, thisWeek, thisMonth] .map(post => { return { ...post, created: DateTime.fromISO(post.created) } })
On vient d'enlever le type post à la constante, on va corriger ça plus tard...
-
Il reste à formater la date dans template:
<span>{{ post.created.toFormat("d MMM") }}</span>
La documentation de luxon donner tous les formats de dates disponibles
Partie 02 - Computed et props
De retour dans Timeline.vue
On va ajouter les filtres pour que les posts apparaissent.
-
Dans la section script, pour ajouter un filtre on doit le faire su notre constante posts comme suit:
const posts = [today, thisWeek, thisMonth] .map(post => { return { ...post, created: DateTime.fromISO(post.created) } }) .filter( post =>{ if (selectedPeriod.value === "Aujourd'hui") { return post.created >= DateTime.now().minus({ day: 1}) } return post })
-
Tester dans le navigateur ça devrait marcher pour la période aujourd'hui! Mais pas pour la semaine et le mois...
-
Il va falloir utiliser le concept de computed en important son utilisation:
import { ref, computed } from "vue"
-
Changer ensuite la ligne de constante de posts pour utiliser computed:
const posts = computed(() => { return [today, thisWeek, thisMonth] .map(post => { return { ...post, created: DateTime.fromISO(post.created) } }) .filter(post => { if (selectedPeriod.value === "Aujourd'hui") { return post.created >= DateTime.now().minus({ day: 1 }) } return post }) })
-
Tester dans le navigateur pour le jour courant ça devrait marcher!
-
On va faire la même chose pour Cette semaine pour que le filtre s'active
- mettre ce if après le if (selectedPeriod.value === "Aujourd'hui") {
if (selectedPeriod.value === "Cette semaine") { return post.created >= DateTime.now().minus({ week: 1 }) }
Il est possible de réduire le map et filter avec reduce, mais on va y revenir...
- mettre ce if après le if (selectedPeriod.value === "Aujourd'hui") {
Refactoring
On va maintenant faire le refactoring de notre application avec des props
- On va créer dans components un composant nommé TimelineItem.vue pour un seul élément de notre Timeline.
TimelineItem.vue avec le contenu de base:
<script setup lang="ts"></script>
<template></template>
<style></style>
-
Dans template, on va aller copier le code de la boucle de nos postes venant de Timeline.vue
<li v-for="post of posts" :key="post.id"> <span>{{ post.title }}</span> | <span>{{ post.created.toFormat("d MMM") }}</span> </li>
-
On va aller le coller et la modifier comme suit:
<li> <span>{{ post.title }}</span> | <span>{{ post.created.toFormat("d MMM") }}</span> </li>
-
Il y aura une erreur pour post puisqu'aucune référence à celui-ci.
-
Dans script, on va donc aller se chercher un props comme suis:
const props = defineProps<{ post: Post }>()
-
Post ne sera pas valide il faut donc l'importer:
import { type Post } from '../posts'
-
Il reste encore une erreur de type pour toFormat
-
Allons la corriger dans posts.ts en faisant une extension de posts avec notre DateTime:
export interface TimelinePost extends Omit<Post, 'created'>{ created: DateTime }
Omit permet d'omettre le created de Post
-
Ensuite dans TimelineItem.vue, on doit changer l'importation suivante:
import { type Post } from '../posts'
-
Pour:
import { type TimelinePost } from '../posts'
-
Et:
const props = defineProps<{ post: Post }>()
-
Pour:
const props = defineProps<{ post: TimelinePost }>()
-
Pour plus de clarté, on peut aussi supprimer l'appel de constante props:
const props = defineProps<{ post: TimelinePost }>()
-
Pour:
defineProps<{ post: TimelinePost }>()
Timeline.vue
-
Ensuite, on doit importer notre composant d'élément de Timeline dans Timeline.vue:
import TimelineItem from './TimelineItem.vue'
-
Ensuite on va remplacer le code de nos éléments suivant:
<li v-for="post of posts" :key="post.id"> <span>{{ post.title }}</span> | <span>{{ post.created.toFormat("d MMM") }}</span> </li>
-
Pour:
<TimelineItem v-for="post of posts" :key="post.id" :post="post" />
:post="post" permet d'associer le props post à la bonne valeur si on l'enlève ça créer une erreur!
-
Dernier ajustement TypeScript dans Timeline.vue on change:
import { type Post, today, thisWeek, thisMonth } from "../posts"
-
Pour:
import { type TimelinePost, today, thisWeek, thisMonth } from "../posts"
-
Et on va l'utiliser pour la valeur computed:
const posts = computed<TimelinePost[]>(() => {
-
Il ne devrait plus avoir d'erreur dans Timeline.vue, ENJOY!
Remise
Aucune remise pour cet atelier.