Développement web avec Vue.js

Mickaël BARON - Version 12-2020

baron.mickael@gmail.com ou baron@ensma.fr

Licence d'utilisation

Creative Commons

Contrat Paternité

Partage des Conditions Initiales à l'Identique

2.0 France

creativecommons.org/licenses/by-sa/4.0

Qui suis-je ?

Ingénieur de Recherche ☀️

  • Recherche dans l'équipe Ingénierie des Données et des moDèles (IDD)
  • Valorisation des plateformes logicielles
  • « Coach technique » auprès des usagés

Responsable communauté 🌑

  • Rubriques : Java, Java Web, Android, Eclipse, Spring et Netbeans
  • Rédacteurs de tutoriels
  • Chiffres
    • 4 M de visiteurs
    • 12 M pages vues/mois
    • 7500 membres
    • 2000 forums

Objectif du cours Vue.js (45 min)

  • Modèle et vue
  • Templating (interpolation et directives)
  • Composants (créer et instancier)
  • Communiquer entre les composants
  • Structure d'un projet Vue.js
  • Routage
  • Outils pour Vue.js

Disclaimer 🧎🏻‍♂️

Pas le temps ⏳ de tout traiter

  • Appeler des services web
  • Tester
  • Déployer

Expérimentation via des exercices

Prérequis minimum

  • Langages à balises (XML, HTML)
  • Notions élémentaires JavaScript

Version supportée dans ce cours

2.x

Historique

Origine

  • Projet open source
  • Evan You, ancien Google, a travaillé sur Angular
  • /vuejs/vue
  • Léger : ~ moins de 30 KB min + gzip (V2)
  • Version actuelle : 3

Historique des versions

Popularité

Source : stateofjs.com

Qui utilise Vue.js ?

Source : vue2-introduction

HelloWorld en Vue.js

<html>
  <body>
    <div id="helloworld">
      <input v-model="value" type="text" />
      <p>{{ value }}</p>
    </div>
	
    <!-- Chargement de la bibliothèque -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2"/>
	
    <script>
      new Vue({
        el: '#helloworld',
        data: { value: 'HelloWorld' }
      });
    </script>
  </body>
</html>

HelloWorld en Vue.js

{{value}}

Versatile

Vue.js peut être utilisée de différentes façons

Bibliothèque

Votre code intègre Vue.js

Framework

Vue.js intègre votre code

Vue.js comme bibliothèque

Quand ?

  • Petits projets
  • Dans un code existant (formulaire)

Comment ?

<script src="https://cdn.jsdelivr.net/npm/vue@2">

Avec quoi ?

  • Aucun outil supplémentaire (utilisable maintenant)

Exemples

Vue.js comme framework

Quand ?

  • Projets importants
  • Single-Page-Application (SPA)

Comment ?

  • Fichier Single-File-Component

Avec quoi ?

  • Des outils : npm, Vue-CLI...
$ npm install vue
$ npm install -g @vue/cli

Modèle et vue

Inspiration du modèle d'architecture MVVM
Model-View-ViewModel

Vue.js correspond à la partie ViewModel

Modèle et vue

HelloWorld et le modèle d'architecture MVVM

Différents concepts clés de Vue.js étudiés dans la suite

  • Interpolation : {{ value }}
  • Directive : v-model="value"
  • Données réactives : data
  • Chaque MVVM est un composant

Templating avec Vue.js

Comment réaliser le databinding ?

Interpolation

Directive

Interpolation Vue.js

Liaison unidirectionnelle : modèle ➡️ vue

Utilisation de la notation moustache : {{ ... }}

Exemples d'interpolation

{{ myProperty }}
{{ number + 1 }}
{{ ok ? 'OUI' : 'NON' }}
{{ message.split('').reverse().join('') }}

Limites

  • N'interpètre pas du HTML (voir v-html)
  • Inutilisable dans des attributs HTML (voir v-bind)

Directives Vue.js

Attributs HTML spécifiques à Vue.js

  • v-model
  • v-bind
  • Directives conditionnelles
    • v-if
    • v-else
    • v-else-if
    • v-show
  • v-for
  • v-on
  • v-html
  • v-text
  • v-cloak
  • v-pre
  • v-once

Étudions ces directives par l'exemple

Directive : v-model

Liaison bidirectionnelle (modèle ↔️ vue) pour composants <input> <select> <textarea>

<div id="app">
 <input v-model="value"/>
 <textarea v-model="value"/>
 <p>{{value}}</p>
</div>

<script>
 new Vue({
  el: '#app',
  data: { 
   value: 'HelloWorld'
  }
 });
</script>

{{value}}

Directive : v-bind

Liaison unidirectionnelle (modèle ➡️ vue) utilisable dans des attributs HTML (ex. : value, disabled)

Syntaxe 👉 v-bind:attribute...
Syntaxe simplifiée 👉 :attribute...

<div id="app">
 <input v-model="value" />
 <input v-bind:value="value" />
 <input :value="value" />
 <p>{{value}}</p>
</div>
<script>
 new Vue({
  el: '#app',
  data: { value: 'HelloWorld' }
 });
</script>

{{value}}

Directives conditionnelles

Cacher ou afficher un élément HTML

v-if, v-else et v-else-if

  • Effectuer un rendu d'un élément
  • v-else et v-else obligatoirement après v-if

v-show

  • Masquer un élément via la propriété CSS display

Comment choisir entre v-show et v-if ?

  • Permutations fréquentes 👉 v-show

Directives conditionnelles

<div id="app">
 <input v-model="show">
   Afficher Vue.js
 </input>

 <img v-show="show" src="lias.png"/>

 <img v-if="show" src="vue.svg"/>
 <img v-else src="mbaron.jpg"/>
</div>

<script>
 new Vue({
  el: '#app',
  data: { 
   show: true 
  }
 });
</script>
Afficher Vue.js

Directive : v-for

Effectuer plusieurs fois le rendu d'une liste d'éléments

Tableau (items)

v-for="(item, index) in items"
  • item : élément courant (ex. : rouge)
  • index : position (ex. : 1)

Objet (object)

v-for="(value, name, index) in object"
  • value : valeur de la propriété (ex. : mickaël)
  • name : nom de la propriété (ex. : firstname)
  • index : position de la propriété (ex. : 0)

Directive : v-for

<div id="app">
 <p v-for="(val,index) in tab">
  {{val + "," + index}}
 </p>

 <p v-for="(val,name,index) in obj">
  {{val + "," + name + "," + index}}
 </p>
</div>

<script>
 new Vue({
  el: '#app',
  data: {
   tab: ['rouge', 'bleu', 'blanc'],
   obj: { p1: 'val1', p2: 'val2', 
          p3: 'val3'
        }
  }
 });
</script>

{{ val+","+index }}

{{ val + "," + name + "," + index }}

Directive : v-on

Écouter les événements de la partie View pour éxécuter vos traitements

Syntaxe 👉 v-on:event=...
Syntaxe simplifiée 👉 @event=...

Modificateurs d'événements

  • .stop : un évènement stoppé
  • .prevent : rechargement page stoppé
  • .left : uniquement bouton gauche de la souris
  • .once : une seule fois
  • ...

Directive : v-on

<div id="app">
 <button v-on:click="increase">
  Augmenter
 </button>
 <button @click.once="decrease">
  Diminuer
 </button>
 <p>{{ value }}</p>
</div>
<script>
 new Vue({
  el: '#app',
  data: { value: 0 },
  methods: {
   increase() { this.value++; },
   decrease() { this.value--; }
  }
 });
</script>

{{ value }}

Autres directives

v-html : interprète du contenu HTML

v-text : identique à l'interpolation de texte {{ }}

v-once : effectue le rendu une seule fois

v-cloak : bloque l'affichage tant que la compilation Vue.js n'est pas terminée

v-pre : n'effectue pas de compilation, texte brute

v-??? : possibilité de créer ses propres directives personnalisées : en savoir plus

Autres directives

<div id="app">
 <div v-html="html"></div>

 <div v-text="html"></div>
 
 <div v-once>{{value}}</div>
 <input v-model="value" />
 
 <div v-pre>{{value}}</div>
</div>

<script>
 new Vue({
  el: '#app',
  data: {
   html: '<b>HelloWorld</b>',
   value: 'HelloWorld'
  }
 });
</script>
{{value}}
{{value}}

Composant

Mise en place d'un composant ?

  1. Savoir développer un composant ?
    Single-File-Component
  2. Savoir instancier un composant ?
    Balise personnalisée
  3. Savoir écouter un composant ?
    Propriétés calculées et observateurs
  4. Savoir contrôler un composant ?
    Cycle de vie

Développer un composant ?

Deux manières de développer des composants

À la volée

Single-File-Component

Développer un composant ?

Fichier Single-File-Component via format .vue

Template : View

  • HTML
  • Interpolation et directive

Script : ViewModel

  • JavaScript
  • Lien avec le Modèle

Style

  • CSS localisé

Instancier un composant ?

Trois étapes à réaliser depuis un fichier .vue

① Importation (dans la partie script)

import YourComponent from "@/components/yourcomponent.vue";

② Déclaration (dans le composant courant)

components: {
 YourComponent
 ...
}

③ Instanciation (dans la partie View)

<YourComponent/>

Instancier un composant ?

<template>
 <div>
  <Clock/>
  <input v-model="value" />
  <p>{{ value }}</p>
 </div>
</template>

<script>
 import Clock from "@/Clock.vue";
 export default {
  name: "helloworld",
  data() {
   return { value: "HelloWorld" };
  },
  components: { Clock }
 };
</script>

<style/>

{{ value }}

Ecouter composant : calculée

Problématiques

  • Expressions complexes dans la partie View
  • Expressions utilisées plusieurs fois

Propriété calculée (computed)

  • Dépendances vers autres propriétés (option data)

Syntaxe

computed: {
 yourProperty() {
  // Votre code qui dépend d'autres propriétés du composant
 }
}

Ecouter composant : calculée

<div id="app">
 <input v-model="value" type="text"/>
 <p>{{ value }}</p>
 <p>Longueur {{ messageLength }}</p>
</div>

<script>
 new Vue({
  el: '#app',
  data: { 
   value: "HelloWorld" 
  },
  computed: {
   messageLength() {
    return this.value.length;
   }
  }
 });
</script>

{{ value }}

Longueur : {{ messageLength }}

Ecouter composant : calculée

Possibilité de faire des mutateurs calculés (v-model)

<div id="app">
 <input v-model="computedValue" />
 <p>{{ computedValue }}</p>
</div>
<script>
 new Vue({
  el: '#app',
  data: { value: "HelloWorld" },
  computed: {
   computedValue: {
    get() { return this.value; },
    set(newValue) {
     if (newValue.length <= 10) {
      this.value = newValue;
     }
    }
 }}});
</script>

{{ valuebis }}

Ecouter composant : observateur

Problématique

  • Invoquer une fonction à chaque nouvelle valeur d'une propriété

Observateur (watcher)

  • Cible une propriété

Syntaxe

watch: {
 property(val) { // val = nouvelle valeur de property
  // Votre code
 }
}

Ecouter composant : observateur

<div id="app">
 <input v-model="value" type="text" />
 <p>{{ message }}</p>
</div>
<script>
 new Vue({
  data: {
   value: "HelloWorld",
   message: "Parfait"
  },
  watch: {
   value(current) {
    if (current.length > 10) {
     this.message = "Trop grand"
    } else {
     this.message = "Parfait"
    }
   }}
 });
</script>

{{ message }}

Cycle de vie d'un composant

Des étapes intermédiaires

  • beforeCreate
  • created
  • mounted
  • updated
  • ...

Intercepter par du code

  • Ex : invoquer service web : created

Cycle de vie d'un composant

<div id="app">
 <input v-model="value" type="text" />
 <p>{{ value }}</p>
 <p>👇 Display the console</p>
</div>
<script>
 new Vue({
  el: '#app',
  data: { value: "HelloWorld"	},
  created() {
   display("create " + this.value)
  },
  mounted() {
   display("mounted " + this.value)
  },
  updated() {
   display("updated " + this.value)
  },
  ...
 });
</script>

{{ value }}

👇 Display the console

Résumé Contenu d'un composant

Différentes options disponibles pour vos composants

  • data : binding View et Model
  • computed : propriétés calculées
  • watch : propriétés observées
  • lifecycle : intercepteurs
  • methods : vos « fonctions » JavaScript
  • components : composants externes
  • À suivre dans la partie communication : props

Communiquer entre les composants

Composant parent ➡️ composants enfants ?

Composant enfant ➡️ composant parent ?

À tous les composants ?

Communication par référence ?

Communication via un gestionnaire d'états ?

Props

Permet une communication Parent ➡️ Enfant

Principe

  • Enfant : déclarer des Props ~ Propriétés (dans option props)
  • Parent : transmettre des valeurs depuis la balise personnalisée

Valeurs

  • Statique
  • Dynamique via directive v-bind

Props (Enfant)

<template>
 <div>{{ value }} {{ message }}</div>
</template>
					
<script>
 export default {
  name: "clock",
   data() { return { value: "" } },
   props: {
    message: String
   },
   created() {
    setInterval(() => { this.computeClock(); }, 1000);
   },
   methods: {
    computeClock() {
     this.value = new Date().toLocaleTimeString();
    }
   }
 }
</script>

Props (Parent)

<template>
 <div>
  <Clock v-bind:message="value"/>
  <input v-model="value" />
  <p>{{ value }}</p>
 </div>
</template>

<script>
 import Clock from "@/Clock.vue";
 export default {
  name: "helloworld",
  data() {
   return { 
    value: "HelloWorld" 
   };
  },
  components: { Clock }
 };
</script>

{{ value }}

Evénéments personnalisés

Permet une communication Enfant ➡️ Parent

Principe

  • Enfant : émettre un événement $emit
  • Parent : écouter les événements via directive v-on

Événement

  • nom d'identification
  • objet de transfert

Evénéments personnalisés : Enfant

<template>
 <div>
  <button v-on:click="saveClock">{{value}}</button>
 </div>
</template>
<script>
 export default {
  name: "clock",
  data() { return { value: "" } },
  created() { ... },
  methods: {
   computeClock() {
    this.value = new Date().toLocaleTimeString();
   }
   saveClock() {
    this.$emit("save-clock", this.value);
   }
  }
 }
</script>

Evénéments personnalisés : Parent

<template>
 <div>
  <Clock 
   @save-clock="saveClock($event)"/>
  <p>{{ value }}</p>
 </div>
</template>

<script>
 import Clock from "@/Clock.vue";
 export default {
  name: "helloworld",
  data() { ... },
  components: { Clock },
  methods: {
   saveClock(currentClock) {
    this.value = currentClock;
   }
  }
 };
</script>

{{ value }}

Bus d'événements

Permet une communication à tous les composants

Principe

  • Créer une instance Vue
  • import Vue from 'vue';
    export const EB = new Vue();
  • Émettre un événement : $emit
  • S'abonner à un événement : $on
  • Supprimer abonnement : $off

Problème de performance

  • Si oubli suppression abonnement

Bus d'événements : émetteur

<template>
 <div>
  <button v-on:click="saveClock">{{value}}</button>
 </div>
</template>

<script>
 import EB from './event-bus.js';
 export default {
  name: "clock",
  data() { return { value: "" } },
  created() { ... },
  methods: {
   computeClock() { ... }
   saveClock() {
    EB.$emit("save-clock", this.value);
   }
  }
 }
</script>

Bus d'événements : récepteur

<template>
 <div>
  <Clock @save-clock="saveClock"/>
  <p>{{ value }}</p>
 </div>
</template>

<script>
 import EB from './event-bus.js';
 import Clock from "@/Clock.vue";
 export default {
  name: "helloworld",
  data() { ... },
  components: { Clock },
  mounted() {
   EB.$on('save-clock', (val) => {
    this.value = val;
   });
  },
 };
</script>

{{ value }}

Gestionnaire d'état

Partager un modèle de stockage par référence

Principe (solution DIY)

  • Créer un modèle de stockage
  • export const Store = {
     state: {
      // Objet JavaScript
     }
    }
  • Utiliser des mutateurs calculés

Aller plus loin

Gestionnaire d'état

<template>
 <div>{{ clockValue }}</div>
</template>

<script>
 import Store from './store.js';
 export default {
  name: "clock",
  data() { return { sm: Store.state } },
  computed: {
   clockValue: {
    set(value) { this.sm.clockValue = value; },
    get() { return this.sm.clockValue; }
   }
  },
  ...
 }
</script>

Gestionnaire d'état

<template>
 <div>
  <Clock><Clock/>
  <p>{{ clockValue }}</p>
 </div>
</template>
<script>
 import Store from './store.js';
 import Clock from "@/Clock.vue";
 export default {
  data() { 
   return { sm: tore.state } 
  },
  components: { Clock },
  computed: { 
   clockValue: {
    get() { 
     return this.sm.clockValue; 
  }}},
 };
</script>

{{ clockValue }}

Référence directe

Accéder à un composant par référence

Principe (parent ➡️ enfants)

  • Définir nom référence via ref
  • Appeler via $refs.myref

Principe (enfant ➡️ parent)

  • Appeler via $parent

Problème

  • Couplage fort
  • Si le composant n'existe plus ?

Référence directe

<template>
 <div>
  <Clock ref="refClock"><Clock/>
  <button @click="save()">
   Save</button>
  <p>{{ value }}</p>
 </div>
</template>
<script>
 import Clock from "@/Clock.vue";
 export default {
  data() { 
   return { value: 'Empty' }},
  methods: {
   save() {
    this.value = 
     this.$refs.refClock.getClock();
  }}
 };
</script>

{{ value }}

Résumé

Technique Usage Avis
Props Parent ➡️ Enfant 👍
Événement personnalisé Enfant ➡️ Parent 👍
Bus événement Globalité 👎
Gestionnaire états Globalité 👍
Référence Parent ↔️ Enfant 👎👎

Structure d'un projet Vue.js

Routage

Outils pour Vue.JS

Outils pour le développeur

Visual Studio Code

Plugin Vetur

  • Vérification de la syntaxe
  • Complétion de code avec IntelliSense
  • Formattage
  • Extraits de codes prêts à l'emploi (snippets)

Plugin Linting

  • Vérification de la qualité de votre code Vue.js

Vue DevTools

Vue DevTools permet d'introspecter votre application Vue.js depuis le navigateur

Installation

Vue CLI

Outil pour créer des projets Vue.js avec le format .vue

Installation

$ npm install -g @vue/cli @vue/cli-service-global

Création projet

$ vue create hello-world

Exécution pour tester

$ npm run serve

Exécution pour déployer

$ npm run build

Vue CLI UI

Passer par une interface graphique : $ vue ui

Bibliothèques / Frameworks

VuePress : générateur de site statique

Vuex : gestionnaire d'états

Nuxt.js : framework du framework Vue.js

Vuetify : bibliothèque de composants basée sur Materiel Design

Développement mobile sans WebView

Allez plus loin

Aides mémoires

Place aux travaux pratiques

Disponibles sur Github : github.com/mickaelbaron

Intégration dans un existant

Single Page Application (SPA)