Prise en main de Vue.js
Dans les applications web précédentes (avec PHP), le serveur pouvait produire directement les pages HTML à afficher. Avec une architecture reposant sur des APIs, le serveur fournit plutôt des données, tandis que le navigateur prend une plus grande part de responsabilité dans la construction et l’actualisation de l’interface.
Nous pouvons accomplir ce travail avec du JavaScript pur (Vanilla JavaScript), mais ce serait un travail assez hardu. Dans cette page, nous découvrirons Vue.js, un cadriciel (framework) qui facilite la construction d’interfaces interactives, la gestion de leur état et leur mise à jour lorsque les données changent.
Pourquoi un cadriciel côté client
Section intitulée « Pourquoi un cadriciel côté client »Rendre une page HTML interactive avec du JavaScript pur est tout à fait possible.
Voici une page comprenant un compteur de mentions d’appréciation (« kudos ») :
<p>Ce projet a reçu <span id="kudos">0</span> kudo(s).</p><button id="btnKudo">👍</button>
<script> let kudos = 0; const affichage = document.querySelector('#kudos');
document.querySelector('#btnKudo').addEventListener('click', () => { kudos++; affichage.textContent = kudos; // ne pas oublier de mettre l'affichage à jour })</script>Le code fonctionne, mais le programme est chargé de deux opérations (responsabilités) : modifier la donnée et actualiser les éléments du DOM qui l’affichent.
Où est le problème ?
Section intitulée « Où est le problème ? »Avec plusieurs compteurs, une liste filtrée et d’autres éléments, ces mises à jour manuelles se multiplient et deviennent de plus en plus difficile à suivre et effectuer de manière efficace. Un oubli peut entraîner l’affichage d’une information périmée.
Avec Vue, nous décrivons les (diverses) dépendances entre l’interface et les données (réactives). Lorsque ces données changent, Vue met automatiquement à jour les éléments de l’interface concernés.Vue n’est pas le seul cadriciel de ce genre : React, Angular et bien d’autres reposent sur la même idée, avec des syntaxes différentes. Les concepts de cette page se transposent en grande partie de l’un à l’autre.
Installation et outils
Section intitulée « Installation et outils »Deux façons d’utiliser Vue
Section intitulée « Deux façons d’utiliser Vue »Vue peut s’utiliser de deux façons courantes, selon les besoins du projet : (1) pour augmenter et enrichir une page existante ou (2) pour créer une application monopage.
Nous verrons d’abord comment utiliser Vue par CDN pour enrichir une page HTML existante.
Ensuite, nous introduirons la notion de composants .vue, qu’on retrouve dans tout projet Vite.
Cette seconde méthode sera introduite dans ce chapitre et développée dans le suivant.
Quand l’utiliser : pour rendre interactive une page HTML existante, pour ajouter un comportement coté client à une page PHP. Elle ne nécessite aucune installation. Le code Vue s’écrit directement dans la page.
-
Créer la page ou ouvrir une page existante. Récupérer un fichier existant (
.php,.html,.cshtml,…) ou créer un fichier. -
Prévoir l’élément de montage. Dans le
body, identifier un élément (traditionnellement#app, comme<div id="app">) qui servira de contenant pour l’application Vue.
Dans l’élément de montage (#app), nous utilisons la syntaxe{{ message }}, appelée interpolation, pour afficher la valeur d’une donnée.<div id="app"><p>{{ message }}</p></div> -
Importer Vue et monter l’application. À la fin du
body, ajouter un script de typemodulequi va importer Vue depuis le CDN, créer une application et la monter sur#app: Vue prendra alors en charge tout ce que cet élément contient.<script type="module">import { createApp, ref } from 'https://unpkg.com/vue@3.5/dist/vue.esm-browser.js'createApp({setup() {const message = ref('Bonjour de Vue')return { message } // ce qui est retourné est utilisable dans la page},}).mount('#app')</script>La fonction
setup()prépare les données et les fonctions de l’application. Tout ce qu’elle retourne devient utilisable dans le HTML monté. -
Ouvrir la page dans le navigateur. Un double-clic sur
index.htmlsuffit : la page doit afficher « Bonjour de Vue ».
Quand l’utiliser : pour construire une application complète,
découpée en composants. Le code est réparti dans des fichiers .vue,
un par composant.
-
Vérifier Node.js. Le générateur de projet Vue exige une version compatible de Node.js (actuellement
^22.18.0 || >=24.12.0). Si Node.js est déjà installé pour les exercices avec Express, vérifier sa version dans un terminal :Fenêtre de terminal node --version -
Créer le projet. Dans le dossier dans lequel vous souhaitez créer le projet, exécute la commande suivante :
Fenêtre de terminal npm create vue@latestL’assistant posera quelques questions, comme lorsqu’on exécute
npm init. -
Installer les dépendances. Après s’être positionné dans le dossier créé par
npm create vue@latest, exécuter la commande suivante :npm install. Elle télécharge Vue, Vite et les autres paquets nécessaires dans le dossiernode_modules/du projet.Fenêtre de terminal cd cafes-udemnpm install -
Lancer le serveur de développement.
Fenêtre de terminal npm run devLa commande affiche une adresse, en général
http://localhost:5173, à ouvrir dans le navigateur. Le terminal doit rester ouvert : arrêter la commande, avec Ctrl+C, arrête aussi le serveur. -
Repérer les fichiers du projet. La plupart du travail se fait dans
src/.cafes-udem/├── index.html la seule page HTML : elle contient <div id="app">├── package.json├── vite.config.js la configuration de Vite├── public/ fichiers copiés tels quels (images, données)└── src/├── main.js crée l'application et la monte sur #app├── App.vue le composant principal└── components/ les autres composantsLe fichier
main.jsjoue le même rôle que le script de la version CDN : il crée l’application et la monte sur#app.
La différence est que l’application est maintenant un composant,App.vue.src/main.js import { createApp } from 'vue'import App from './App.vue'createApp(App).mount('#app')
Les outils pour faciliter le développement
Section intitulée « Les outils pour faciliter le développement »
- Dans VS Code : l’extension Vue (Official) colore la syntaxe des fichiers
.vue, complète le code et signale les erreurs. - Dans le navigateur : les Vue DevTools permettent d’inspecter les composants et leur état. Si leur intégration a été sélectionnée lors de la création du projet Vite, un bouton permet d’ouvrir les outils. Sinon, on peut utiliser l’extension Vue.js devtools du navigateur, notamment avec une page qui charge Vue par CDN.
Tutoriel : Portfolio interactif
Section intitulée « Tutoriel : Portfolio interactif »Inès, étudiante en développement web, a conçu la page de son portfolio en HTML et en CSS. La page s’affiche bien, mais elle aimerait également :
- un bouton pour activer ou désactiver le mode sombre ;
- des boutons pour filtrer les projets par technologie ;
- des boutons Détails pour afficher les apprentissages associés à chaque projet ;
- une séparation entre les données (profil, compétences et projets) et le HTML qui les affiche.
Le dossier de départ contient les fichiers index.html et style.css.
Nous allons progressivement enrichir cette page.
Tout le code du portfolio utilisera Vue par CDN et la fonction setup().
Étape 1 : monter Vue sur la page (createApp)
Section intitulée « Étape 1 : monter Vue sur la page (createApp) »On commence par entourer le contenu de la page d’un conteneur #app, puis on ajoute le script à la fin du body.
Les informations du profil sont définies dans le script, puis affichées dans le HTML par interpolation, avec la syntaxe
{{ }}.
Le profil ne change pas au cours de cet exemple : un objet JavaScript ordinaire suffit.
<div id="app" v-cloak> <div class="site"> <header class="entete conteneur"> <!-- ... --> <h1>{{ profil.nom }}</h1> <p class="titre">{{ profil.titre }}</p> </header> <!-- ... le reste de la page --> </div></div>
<script type="module"> import { createApp, ref, computed } from 'https://unpkg.com/vue@3.5/dist/vue.esm-browser.js'
createApp({ setup() { const profil = { nom: 'Inès Gagnon', titre: "Étudiante en techniques de l'informatique, profil développement web", courriel: 'ines.gagnon@exemple.test', disponible: true, }
return { profil } }, }).mount('#app')</script>Les fonctions
refetcomputedseront utilisées aux étapes suivantes.
Étape 2 : réagir aux clics et directives
Section intitulée « Étape 2 : réagir aux clics et directives »Pour réagir à un événement, on utilise la directive v-on ou son raccourci @, par exemple @click pour un clic.
Ce projet a reçu 0 kudo(s).
Le code de cette démonstration est présenté dans un composant
.vue. Dans le portfolio, les déclarations se placent plutôt dans la fonctionsetup()et les variables utilisées dans le HTML doivent être incluses dans lereturn. La réactivité et les directives fonctionnent de la même façon.
<script setup>import { ref } from 'vue'
const mentions = ref(0)</script>
<template> <div class="demo not-content"> <p>Ce projet a reçu {{ mentions }} kudo(s).</p> <div class="ligne"> <button @click="mentions++">👍</button> <button @click="mentions = 0">Réinitialiser</button> </div> </div></template>La fonction ref()
Section intitulée « La fonction ref() »ref(valeur) crée une référence réactive. Elle est utilisée lorsque l’information (valeur)
peut changer pendant l’utilisation et que l’interface en dépend.
Elle permet à Vue de suivre les changements de la valeur et mettre à jour ce qui en dépend.
Lorsqu’on utilise ref:
- Dans le script JS, on accède à la valeur avec
.value:mentions.value++. - Dans le gabarit Vue (
<template>ou HTML monté par Vue), la référence est automatiquement déballée : on écrit{{ mentions }}ou@click="mentions++", sans.value.
Directives
Section intitulée « Directives »En Vue.js, une directive est un attribut spécial que l’on ajoute à un élément HTML, , généralement écrit avec le préfixe v-.
Elle indique à Vue comment cet élément doit se comporter ou être mis à jour en fonction des données de l’application.
@click utilisé plus haut est en réalité le raccourci de v-on:click : la directive exécute une expression ou appelle une fonction en réponse à un clic.
Application dans le portfolio
Section intitulée « Application dans le portfolio »Dans notre exemple de Portfolio, le thème devient une donnée, que le bouton inverse :
Dans <script>
Section intitulée « Dans <script> »const sombre = ref(false)
return { profil, sombre }Dans #app (HTML)
Section intitulée « Dans #app (HTML) »<button @click="sombre = !sombre">{{ sombre ? 'Mode clair' : 'Mode sombre' }}</button>Il reste encore à définir les règles de style rattachées au mode sombre.
Étape 3 : lier des attributs (directive v-bind)
Section intitulée « Étape 3 : lier des attributs (directive v-bind) »Retour sur la fonction ref()
Section intitulée « Retour sur la fonction ref() »Nous avons vu que la fonction ref() permet de créer une donnée réactive :
lorsque sa valeur change, Vue peut détecter ce changement et actualiser les parties
de l’interface qui en dépendent.
Cependant, ref() ne détermine pas comment cette donnée doit apparaître dans le HTML.
C’est le rôle des expressions et des directives utilisées dans le gabarit (<template>).
Jusqu’à présent, nous avons utilisé l’interpolation {{ }} pour afficher une valeur
dans le contenu d’un élément :
<p>Mode sombre : {{ sombre }}</p>Mais comment utiliser cette même donnée pour modifier un attribut HTML, comme src, href, disabled ou class ?
C’est précisément le rôle de la directive v-bind.
La directive v-bind
Section intitulée « La directive v-bind »La directive v-bind (ou son raccourci :) permet de lier un attribut HTML à une expression JavaScript.
Lorsque les données réactives utilisées dans cette expression changent, Vue actualise automatiquement l’attribut.
Par exemple :
<button v-bind:aria-pressed="sombre"> Changer de thème</button>ou (son équivalent):
<button :aria-pressed="sombre"> Changer de thème</button>L’attribut aria-pressed reçoit la valeur de sombre et est actualisé lorsque cette valeur change.
Le cas particulier de class
Section intitulée « Le cas particulier de class »Pour class, Vue accepte un objet : chaque classe est appliquée si sa valeur est vraie.
Nous pouvons utiliser :class pour déterminer dynamiquement les classes d’un élément.
Par exemple :
<div class="site" :class="sombre ? 'sombre' : ''">Cette expression ajoute la classe sombre uniquement lorsque la variable sombre vaut true.
Cependant, puisque cette opération est très fréquente, Vue propose une syntaxe particulière pour class : un objet JavaScript dont les propriétés représentent des classes CSS.
<div class="site" :class="{ sombre: sombre }">Décomposons cette expression :
{ }délimite un objet JavaScript.- Le premier
sombrecorrespond au nom de la classe CSS. - Le second
sombrecorrespond à la variable JavaScript qui détermine si cette classe doit être appliquée.
Pour chaque propriété de l’objet, Vue examine la valeur associée : si elle est vraie (truthy), la classe est ajoutée; sinon, elle est retirée.
- Si
sombreestfalse:class="site" - Si
sombreesttrue:class="site sombre"
Il est également possible de gérer plusieurs classes indépendamment :
<div :class="{ sombre: sombre, actif: estActif, erreur: aErreur }">Chaque classe est ajoutée ou retirée selon la valeur de l’expression correspondante.
Exemple : changer le thème d’une page
Section intitulée « Exemple : changer le thème d’une page »Thème actuel : clair
<script setup>import { ref } from 'vue'
const sombre = ref(false)</script>
<template> <div class="demo" :class="{ sombre: sombre }"> <p>Thème actuel : {{ sombre ? 'sombre' : 'clair' }}</p> <button @click="sombre = !sombre" :aria-pressed="sombre"> Changer de thème </button> </div></template>Application au portfolio
Section intitulée « Application au portfolio »Dans le portfolio, la feuille de style prévoit déjà une classe sombre.
Il suffit de la lier à la donnée :
<div class="site" :class="{ sombre: sombre }">L’élément conserve sa classe site, tandis que la présence de sombre dépend de la variable réactive.
Étape 4 : afficher des listes (directive v-for)
Section intitulée « Étape 4 : afficher des listes (directive v-for) »La directive v-for répète un élément pour chaque valeur d’un tableau.
Lorsqu’on l’utilise, l’attribut :key donne à chaque répétition un identifiant unique,
qui permet à Vue de savoir quel élément a été ajouté, retiré ou déplacé.
- HTML
- CSS
- JavaScript
<script setup>import { ref } from 'vue'
const competences = ref([ { id: 1, nom: 'HTML' }, { id: 2, nom: 'CSS' }, { id: 3, nom: 'JavaScript' },])const suggestions = ['PHP', 'MySQL', 'Git', 'Vue.js']let prochainId = 4
function ajouter() { const nom = suggestions[(prochainId - 4) % suggestions.length] competences.value.push({ id: prochainId++, nom })}
function retirer(id) { competences.value = competences.value.filter((c) => c.id !== id)}</script>
<template> <div class="demo"> <ul> <li v-for="competence in competences" :key="competence.id"> {{ competence.nom }} <button @click="retirer(competence.id)" aria-label="Retirer">×</button> </li> </ul> <p v-if="competences.length === 0" class="note">Aucune compétence pour l'instant.</p> <button @click="ajouter">Ajouter une compétence</button> </div></template>C’est le même travail de refactoring que l’extraction des données que nous avions fait sur les pages dynamiques :
- En PHP, une boucle
foreachproduisait les cartes à partir d’un tableau \ - Ici,
v-forle fait dans le navigateur. Dans le portfolio, les compétences et les projets passent dans le script :
const competences = ['HTML', 'CSS', 'JavaScript', 'PHP', 'MySQL', 'Express', 'Vue', 'Git']
const projets = [ { id: 1, titre: 'Recettes de grand-maman', technos: ['HTML', 'CSS'], description: "Site vitrine de recettes familiales, adapté à toutes les tailles d'écran.", appris: 'les grilles CSS et les requêtes média.' }, // … les cinq autres projets, avec chacun un id distinct]Les six blocs <article> écrits à la main se réduisent à un seul, répété :
<article v-for="projet in projets" :key="projet.id" class="projet"> <h3>{{ projet.titre }}</h3> <p>{{ projet.description }}</p> <div class="technos"> <span v-for="techno in projet.technos" :key="techno">{{ techno }}</span> </div> <p class="appris">Ce que j'ai appris : {{ projet.appris }}</p> <button>Détails</button></article>Étape 5 : filtrer avec une propriété calculée
Section intitulée « Étape 5 : filtrer avec une propriété calculée »Le filtre retenu est une donnée, filtre. La liste affichée, elle, se déduit de cette donnée et des projets : c’est le rôle d’une propriété calculée.
4 projet(s)
- Site de recettes (HTML, CSS)
- Quiz interactif (HTML, JavaScript)
- Catalogue en ligne (PHP, MySQL)
- Météo en direct (JavaScript, Vue)
<script setup>import { ref, computed } from 'vue'
const projets = [ { id: 1, titre: 'Site de recettes', technos: ['HTML', 'CSS'] }, { id: 2, titre: 'Quiz interactif', technos: ['HTML', 'JavaScript'] }, { id: 3, titre: 'Catalogue en ligne', technos: ['PHP', 'MySQL'] }, { id: 4, titre: 'Météo en direct', technos: ['JavaScript', 'Vue'] },]const technos = ['Toutes', 'HTML', 'CSS', 'JavaScript', 'PHP', 'MySQL', 'Vue']
const filtre = ref('Toutes')
const projetsFiltres = computed(() => { if (filtre.value === 'Toutes') { return projets }
return projets.filter((p) => p.technos.includes(filtre.value))})</script>
<template> <div class="demo"> <div class="ligne"> <button v-for="techno in technos" :key="techno" :class="{ actif: techno === filtre }" @click="filtre = techno" > {{ techno }} </button> </div> <p class="note">{{ projetsFiltres.length }} projet(s)</p> <ul> <li v-for="projet in projetsFiltres" :key="projet.id"> {{ projet.titre }} ({{ projet.technos.join(', ') }}) </li> </ul> </div></template>Application au portfolio
Section intitulée « Application au portfolio »Pour le portfolio, les boutons de filtre sont eux aussi produits à partir des données : la liste des technologies se déduit des projets, sans doublon.
const filtre = ref('Toutes')
// Les technologies des projets, sans doublon, précédées de « Toutes »const technos = ['Toutes', ...new Set(projets.flatMap((p) => p.technos))]
const projetsFiltres = computed(() => { if (filtre.value === 'Toutes') { return projets }
return projets.filter((p) => p.technos.includes(filtre.value))})<div class="filtres"> <button v-for="techno in technos" :key="techno" :class="{ actif: techno === filtre }" @click="filtre = techno" >{{ techno }}</button></div>
<article v-for="projet in projetsFiltres" :key="projet.id" class="projet">VérificationPourquoi ne pas filtrer directement le tableau projets, avec projets = projets.filter(…), au clic sur un bouton ?
Parce que les projets écartés seraient perdus : revenir à « Toutes », ou passer d’une technologie à une autre, n’aurait plus rien à afficher. La propriété calculée laisse intactes les données d’origine, et en déduit à chaque changement la liste à afficher.
Étape 6 : afficher sous condition (directive v-if, v-show)
Section intitulée « Étape 6 : afficher sous condition (directive v-if, v-show) »Deux directives affichent un élément sous condition, de deux façons différentes.
Disponible pour un stage cet été
<script setup>import { ref } from 'vue'
const disponible = ref(true)const details = ref(false)</script>
<template> <div class="demo"> <p v-if="disponible" class="oui">Disponible pour un stage cet été</p> <p v-else class="non">Stage déjà trouvé, merci !</p>
<p v-show="details" class="note">Stage de huit semaines, à partir de mai, en développement web.</p>
<div class="ligne"> <button @click="disponible = !disponible">Changer la disponibilité</button> <button @click="details = !details">{{ details ? 'Masquer' : 'Afficher' }} les détails</button> </div> </div></template>- La directive
v-ifcrée ou retire réellement l’élément de la page. Elle se combine avecv-else-ifetv-else. - La directive
v-showlaisse l’élément dans la page, et le masque avecdisplay: none.
Dans l’inspecteur du navigateur, le paragraphe de
v-ifdisparaît du HTML, tandis que celui dev-showy reste, masqué.
v-ifest recommandé lorsque l’élément à afficher dépend d’une condition qui change rarement, ou lorsque l’élément ne doit pas exister du tout.v-showest recommandé lorsque l’élément est affiché et masqué souvent
Application au portfolio
Section intitulée « Application au portfolio »Dans le portfolio, le bandeau de disponibilité dépend du profil et les détails de chaque projet s’affichent à la demande. Pour ouvrir chaque panneau indépendamment, nous conservons les identifiants des projets ouverts dans une référence réactive :
Dans <script>
Section intitulée « Dans <script> »const ouverts = ref([]) // identifiants des projets dont les détails sont affichés
function basculerDetails(id) { if (ouverts.value.includes(id)) { ouverts.value = ouverts.value.filter((projetId) => projetId !== id) } else { ouverts.value.push(id) }}Dans #app(HTML)
Section intitulée « Dans #app(HTML) »<span v-if="profil.disponible" class="dispo">Disponible pour un stage à l'été 2027</span>
<p v-show="ouverts.includes(projet.id)" class="appris">Ce que j'ai appris : {{ projet.appris }}</p><button @click="basculerDetails(projet.id)" :aria-expanded="ouverts.includes(projet.id)"> {{ ouverts.includes(projet.id) ? 'Masquer' : 'Détails' }}</button>Sans oublier d’ajouter au return tout ce que le HTML utilise : return { profil, competences, sombre, filtre, technos, projetsFiltres, ouverts, basculerDetails }.
À toi de jouer : améliorer le portfolio
Section intitulée « À toi de jouer : améliorer le portfolio »Avant d’ouvrir la version complète, reprends ton portfolio et réalise ces modifications sans copier de solution :
- Afficher le nombre de projets visibles à côté du titre « Projets ». Ce nombre doit changer lorsqu’on sélectionne une technologie.
- Ajouter un bouton « Réinitialiser le filtre » qui apparaît uniquement quand le filtre est différent de « Toutes ».
- Diagnostiquer un problème d’état : si l’on remplaçait le tableau
ouvertspar une seule référenceconst detailsOuverts = ref(false)utilisée dans toutes les cartes, que se passerait-il ? Pourquoi ? - Expliquer ses choix : parmi
projets,sombre,filtre,projetsFiltresetouverts, lesquels sont des données ordinaires, des états réactifs ou des valeurs calculées ?
VérificationPourquoi une seule variable booléenne partagée entre tous les panneaux de détails ne permettrait-elle pas de les ouvrir indépendamment ?
Toutes les cartes liraient le même booléen : un clic ouvrirait ou fermerait leurs panneaux simultanément. Pour distinguer les cartes, il faut conserver un état par projet, par exemple la liste des identifiants ouverts.
Version complète de référence
Section intitulée « Version complète de référence »Le fichier suivant réunit les fonctionnalités des six étapes. Il ne contient pas les ajouts demandés dans l’activité précédente, à réaliser par toi-même.
Le fichier index.html complet du portfolio
<!DOCTYPE html><html lang="fr"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Inès Gagnon · Développeuse web en formation</title> <link rel="stylesheet" href="style.css"> <style>[v-cloak] { display: none; }</style></head><body><div id="app" v-cloak><div class="site" :class="{ sombre: sombre }">
<header class="entete conteneur"> <div class="haut"> <span class="marque">IG</span> <button @click="sombre = !sombre">{{ sombre ? 'Mode clair' : 'Mode sombre' }}</button> </div> <h1>{{ profil.nom }}</h1> <p class="titre">{{ profil.titre }}</p> <span v-if="profil.disponible" class="dispo">Disponible pour un stage à l'été 2027</span> </header>
<main class="conteneur"> <section> <h2>À propos</h2> <p>Je construis des sites et des applications web, du serveur jusqu'à l'interface. J'aime les projets utiles, les interfaces claires et le code qu'on peut relire six mois plus tard.</p> </section>
<section> <h2>Compétences</h2> <ul class="puces"> <li v-for="competence in competences" :key="competence">{{ competence }}</li> </ul> </section>
<section> <h2>Projets</h2>
<div class="filtres"> <button v-for="techno in technos" :key="techno" :class="{ actif: techno === filtre }" @click="filtre = techno" >{{ techno }}</button> </div>
<div class="grille"> <article v-for="projet in projetsFiltres" :key="projet.id" class="projet"> <h3>{{ projet.titre }}</h3> <p>{{ projet.description }}</p> <div class="technos"> <span v-for="techno in projet.technos" :key="techno">{{ techno }}</span> </div> <p v-show="ouverts.includes(projet.id)" class="appris">Ce que j'ai appris : {{ projet.appris }}</p> <button @click="basculerDetails(projet.id)" :aria-expanded="ouverts.includes(projet.id)"> {{ ouverts.includes(projet.id) ? 'Masquer' : 'Détails' }} </button> </article> </div> </section>
<section class="contact"> <h2>Contact</h2> <p>Écrivez-moi à <a :href="`mailto:${profil.courriel}`">{{ profil.courriel }}</a>.</p> </section> </main>
<footer class="pied conteneur">{{ profil.nom }} · Montréal</footer></div></div>
<script type="module"> import { createApp, ref, computed } from 'https://unpkg.com/vue@3.5/dist/vue.esm-browser.js'
createApp({ setup() { const profil = { nom: 'Inès Gagnon', titre: "Étudiante en techniques de l'informatique, profil développement web", courriel: 'ines.gagnon@exemple.test', disponible: true, }
const competences = ['HTML', 'CSS', 'JavaScript', 'PHP', 'MySQL', 'Express', 'Vue', 'Git']
const projets = [ { id: 1, titre: 'Recettes de grand-maman', technos: ['HTML', 'CSS'], description: "Site vitrine de recettes familiales, adapté à toutes les tailles d'écran.", appris: 'les grilles CSS et les requêtes média.' }, { id: 2, titre: 'Quiz des capitales', technos: ['HTML', 'CSS', 'JavaScript'], description: 'Quiz chronométré avec un score final et un tableau des meilleurs résultats.', appris: 'manipuler le DOM et réagir aux événements.' }, { id: 3, titre: "Catalogue de l'érablière", technos: ['PHP', 'MySQL'], description: 'Catalogue de produits avec ajout, modification et suppression.', appris: 'les requêtes préparées et le patron POST-Redirect-GET.' }, { id: 4, titre: 'API de réservations', technos: ['JavaScript', 'Express'], description: 'API REST pour réserver des places de spectacle.', appris: 'les codes de statut et la validation côté serveur.' }, { id: 5, titre: 'Météo du campus', technos: ['JavaScript', 'Vue'], description: 'Tableau de bord météo alimenté par une API publique.', appris: 'la réactivité et les propriétés calculées.' }, { id: 6, titre: 'Ce portfolio', technos: ['HTML', 'CSS', 'Vue'], description: 'Une page HTML statique, rendue interactive avec Vue.', appris: 'monter une application Vue sur une page existante.' }, ]
// État de l'interface const sombre = ref(false) const filtre = ref('Toutes') const ouverts = ref([]) // identifiants des projets dont les détails sont affichés
// Les technologies des projets, sans doublon, précédées de « Toutes » const technos = ['Toutes', ...new Set(projets.flatMap((p) => p.technos))]
const projetsFiltres = computed(() => { if (filtre.value === 'Toutes') return projets return projets.filter((p) => p.technos.includes(filtre.value)) })
function basculerDetails(id) { if (ouverts.value.includes(id)) { ouverts.value = ouverts.value.filter((projetId) => projetId !== id) } else { ouverts.value.push(id) } }
return { profil, competences, sombre, filtre, technos, projetsFiltres, ouverts, basculerDetails } }, }).mount('#app')</script></body></html>Découvrir les composants .vue
Section intitulée « Découvrir les composants .vue »Jusqu’ici, le portfolio utilisait une page HTML et un script chargé par CDN. Pour les applications plus importantes, nous pouvons répartir l’interface en composants réutilisables dans des fichiers .vue, grâce à un projet Vite.
Voici un composant simple :
<script setup>import { ref } from 'vue'
const message = ref('Bonjour de Vue')</script>
<template> <p>{{ message }}</p></template>
<style scoped>p { font-weight: 600; }</style><script setup> — L’état et le comportement
Section intitulée « <script setup> — L’état et le comportement »Cette section contient le code JavaScript du composant : imports, données, état réactif, calculs et fonctions utilisées par l’interface.
Les déclarations faites au niveau principal sont directement accessibles dans le gabarit, sans return explicite.
<script setup>import { ref } from 'vue'
const message = ref('Bonjour de Vue')</script><template> — La structure (gabarit)
Section intitulée « <template> — La structure (gabarit) »Cette section contient le HTML enrichi par la syntaxe de Vue.
On y utilise les valeurs et fonctions déclarées dans <script setup> :
<template> <p>{{ message }}</p></template><style scoped> — L’apparence
Section intitulée « <style scoped> — L’apparence »Cette section optionnelle définit des règles CSS. Avec scoped, Vue limite principalement la portée des sélecteurs aux éléments du composant, plutôt qu’à l’ensemble de l’application.
<style scoped>p { font-weight: 600; }</style>Les styles globaux peuvent toujours affecter le composant. Les règles de la cascade CSS (spécificité, ordre, etc.) continuent de déterminer quels styles s’appliquent : scoped ne garantit pas la priorité des règles locales sur toutes les règles globales.
Deux syntaxes pour les mêmes concepts
Section intitulée « Deux syntaxes pour les mêmes concepts »Portfolio par CDN (setup()) |
Fichier .vue (<script setup>) |
|---|---|
On appelle createApp({ setup() { … } }) dans un script de la page HTML. |
On écrit les déclarations dans la section <script setup> du composant. |
setup() retourne les variables et fonctions à rendre accessibles au HTML. |
Les déclarations principales sont accessibles dans <template> sans return. |
Les directives s’écrivent dans le HTML à l’intérieur de #app. |
Les directives s’écrivent dans <template>. |
| Le navigateur charge directement Vue par CDN. | Vite compile les fichiers .vue et sert l’application. |
Les mécanismes ref, computed, v-bind, v-for, v-if et @click restent les mêmes. On ne place pas un bloc <script setup> directement dans une page HTML chargée par CDN : cette syntaxe doit être compilée comme partie d’un fichier .vue.
Et ensuite
Section intitulée « Et ensuite »Dans le prochain cours et pour la suite, nous utiliserons un projet Vite pour construire une interface organisée en composants et consommer des données provenant d’une API.
Récapitulatif
Section intitulée « Récapitulatif »| Concept | Syntaxe | Rôle |
|---|---|---|
| Interpolation | {{ expression }} |
afficher une valeur dans le texte, échappée |
| Donnée réactive | ref(valeur), .value dans le script |
une valeur dont les changements mettent la page à jour |
| Événement | @click="…" (v-on) |
réagir à une action de la personne |
| Liaison d’attribut | :class="…" (v-bind) |
donner à un attribut la valeur d’une expression |
| Liste | v-for="x in liste" avec :key |
répéter un élément pour chaque valeur |
| Propriété calculée | computed(() => …) |
déduire une valeur d’autres données, sans la recalculer inutilement |
| Condition | v-if, v-else-if, v-else, v-show |
afficher un élément sous condition |
| Montage par CDN | createApp({ setup() { … return … } }).mount('#app') |
rendre interactive une page existante ; seul le contenu de #app sert de gabarit |
| Composant monofichier | <script setup>, <template>, <style scoped> |
organiser une interface en composants .vue, compilés avec Vite |
Quiz. Dans <script setup>, on écrit const total = ref(0). Comment augmenter total de 1 dans une fonction du script ?
Quiz. Une liste filtrée doit se mettre à jour chaque fois que le filtre change. Quel outil convient le mieux ?
Quiz. Un panneau d'aide s'ouvre et se ferme des dizaines de fois par minute. Quelle directive est la plus adaptée ?
Quiz. Une page charge Vue par CDN. Une directive :class placée sur l'élément div id='app' n'a aucun effet. Pourquoi ?