Aller au contenu

Outils Vue.js

Vue n’est pas le framework le plus utilisé (c’est surement React), mais c’est l’un des plus simple à appréhender. Et ainsi comprendre le principe global des ces outils.

Documentation#

Commencer#

Pour commencer à utiliser Vue, pas besoin de configuration complexe, il suffit de charger la librairie. Ici on utilisera Vue 3, avec le style «Options API».

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

Et de déclarer une nouvelle instance de Vue avec vos données dans data (coté JS), et ces mêmes données coté template/rendu (donc HTML)

<div id="app">
  {{ message }}
</div>
Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
    };
  },
}).mount('#app');

Notez que data est une fonction qui retourne un objet littéral contenant nos données.

On parle alors de binding. Les données coté JS sont liées au rendu HTML. Dans Vue, le binding classique se fait avec la notation {{ }}. Pour binder la valeur à un attribut, il faut utiliser la directive v-bind:

<div v-bind:title="message"></div>

Ou simplement la version raccourcie avec deux-points

<div :title="message"></div>

Pour éviter de mettre trop de logique dans le template (HTML), on peut créer des propriétés calculées, à définir dans l’option computed (et non plus data). Par exemple, pour afficher le message en sens inverse

Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
    };
  },
  computed: {
    reversedMessage () {
      // `this` pointe sur l'instance Vue
      return this.message.split('').reverse().join('');
    },
  },
}).mount('#app');
<div id="app">
  {{ reversedMessage }}
</div>

Notez que reversedMessage est une fonction, mais qui est utilisée telle une propriété

Pour les formulaires, il est souvent nécessaire de binder dans les 2 sens. Du code vers l’interface, mais également de l’interface vers le code (case à cocher, champ texte, etc.). Pour cela, on utilisera la directive v-model.

<input type="text" v-model="message">
<input type="checkbox" v-model="cochee">
Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
      cochee: true,
    };
  },
}).mount('#app');

Ainsi, il n’est pas forcément nécessaire de créer d’évènements. Au chargement de la page, le champ texte contient le texte "Hello Vue !" et la checkbox est cochée. Si l’utilisateur change le texte depuis l’interface, la variable message est mise à jour dans le code. Si il décoche la checkbox, idem, la variable cochee est mise à jour. Et les éventuelles propriétés calculées sont recalculées en temps-réel.

Vue propose également d’autres directives intéressantes qui permettent d’ajouter de la logique dans vos templates :

  • v-if/v-else pour de l’affichage conditionnel
  • v-for pour générer des éléments HTML à la volée

Les évènements#

La directive v-on: permet d’ajouter des évènements qui appelleront des méthodes stockées dans methods

Vue.createApp({
  data() {
    return {
      message: 'Hello Vue !',
    };
  },
  methods: {
    hello () {
      alert('Hello !');
    },
  },
}).mount('#app');
<button v-on:click="hello">Say Hello</button>

Ou simplement la version raccourcie avec @

<button @click="hello">Say Hello</button>

Pour passer des variables, vous utiliserez

<button @click="hello('world', $event)">Say Hello</button>

Il existe également des modificateurs pour gérer quelques actions classiques :

<!-- call event.preventDefault() -->
<form @submit.prevent="submit"></form>

<!-- call submit() when the keyCode is 13 (enter) -->
<input @keyup.13="submit">

Les composants#

Il n’est bien entendu pas souhaitable de mettre toute votre application dans une seule instance Vue. L’un des concepts des frameworks est de découper une interface en composants réutilisables. Chaque composant aura accès à ses propres données, évènements, etc.

let app = Vue.createApp({});

app.component('my-component', {
  template: '<div>{{reversedMessage}}</div>',
  data () {
    return {
      message: 'Hello Component !',
    };
  },
  computed: {
    reversedMessage () {
      return this.message.split('').reverse().join('');
    },
  },
});

app.mount('#app');
<div id="app">
  <my-component></mycomponent>
</div>

Nous donne :

<div id="app">
  <div>! tnenopmoC olleH</div> <!-- «Hello Component !» à l’envers-->
</div>

Il est également possible de créer des composants par fichier (.vue) qui contiennent le HTML, JS et CSS associé. Cela nécessite l’utilisation d’un builder.

Passer des propriétés#

Lorsqu’il est instancié, un composant peut recevoir des propriétés depuis son parent. C’est même d’ailleurs la seule méthode de communication entre éléments (ce que l’on appelle le one-way data flow). Il faut pour cela que le composant enfant les définissent depuis props (avec ou sans validation du type attendu)

app.component('my-component', {
  template: '<div>{{reversedMessage}}</div>',
  props: {
    message: {
      type: String,
      default: '...',
    },
  },
});
<div id="app">
  <my-component message="Hello from Parent"></my-component>
  <my-component></my-component>
</div>

donne

<div id="app">
  <div>Hello from Parent</div>
  <div>...</div>
</div>

La communication priviligiée est toujours du composant parent vers le composant enfant. Il est toutefois possible de faire l’inverse, grâce aux évènements. Voici un exemple de composant button-counter extrait de la doc officielle :

<div id="app">
  <p>{{ total }}</p>
  <button-counter @increment="incrementTotal"></button-counter>
</div>
let app = Vue.createApp({
  data() {
    return {
      total: 0,
    };
  },
  methods: {
    incrementTotal () {
      this.total++;
    },
  },
});

app.component('button-counter', {
  template: '<button @click="incrementCounter">+</button>',
  methods: {
    incrementCounter () {
      this.$emit('increment');
    },
  },
});

app.mount('#app');

Dans des cas simples, cela peut suffire. Dans les autres cas, il peut être préconisé d’utiliser un «gestionnaire d’état» plus global, comme Vuex (voir plus loin).

Cycle de vie#

Chaque composant a un cycle de vie, et des hooks nous permettent d’exécuter des actions à des moments précis. Le plus important étant mounted

app.component('my-component', {
  template: '<div>{{message}}</div>',
  mounted () {
    console.log('Composant attaché (monté) au DOM');
  },
});

Il existe également :

  • created() le composant est créé
  • updated() le composant a reçu de nouvelles propriétés (et donc s’est mis à jour)
  • destroyed() le composant est supprimé
  • etc.

Voir : https://vuejs.org/v2/api/#Options-Lifecycle-Hooks

Réactivité#

Bien entendu, le premier intérêt étant la réactivité, un composant se met à jour automatiquement si ses donnés changent ou s’il reçoit de nouvelles données de son parent. Par exemple, un composant heure qui est mis à jour depuis son parent, toutes les 1 secondes.

Vue décide ou non d'appliquer cette mise à jour dans le DOM si nécessaire.

let app = Vue.createApp({
  data() {
    return {
      timeString: '00:00:00',
    };
  },
  mounted () {
    this.setTime();
  },
  methods: {
    setTime () {
      requestAnimationFrame(() => {
        this.timeString = new Date().toLocaleTimeString('fr-FR');
        this.setTime();
      });
    },
  },
});

app.component('heure', {
  template: '<p>{{time}}</p>',
  props: ['time'],
});

app.mount('#app');
<div id="app">
  <heure :time="timeString"></heure>
</div>

Exercice - Vue#

Le but de cet exercice est de créer le champ texte sur le principe de celui utilisé par Twitter. C’est à dire, un simple champ texte, limité à un nombre défini de caractères, un bouton de validation et un bouton d’ajout de photo qui limite encore plus le nombre de caractères.

C’est en fait exactement la même chose que l’exercice de rappels (fait en Plain ou Vanilla JS), mais cette fois-ci en utilisant Vue. Par exemple, pour la mise à jour du nombre de caractères restants, lors de la saisie du message :

  • Chargez la librairie Vue
  • Créez une nouvelle instance de Vue
    • Stockez les données globales dans data (le texte, le nombre de caractères max)
  • Créez une propriété calculée qui calcule le nombre de caractères restants et utilisez cette valeur pour l’affichage
  • Utilisez v-model pour mettre à jour la valeur du texte (le nombre de caractères restants devrait se mettre à jour automatiquement)
  • Continuez les fonctionnalités de base en suivant ce principe

Enfin, ajoutez une nouvelle fonctionnalité :

  • L’utilisateur doit pouvoir tweeter et ses tweets doivent apparaitre sous la tweetbox (la partie serveur n’est pas demandée). Pour cela :
    • Créez une liste de tweets
    • A chaque clic sur le bouton, ajoutez les données du tweet actuel (message, image) dans la liste
    • Générez des blocs HTML basés sur cette liste (utilisation de v-for, v-if)
    • Note: pour les images, utilisez un service de récupération d’image aléatoire comme https://picsum.photos

Vuex#

Nous avons vu que chaque composant gère son propre état, et que nous ne pouvons passer des données que dans un seul sens (il n’est pas possible de facilement envoyer des données d’un composant enfant vers un composant parent). Dans le cas d’une application complexe, avec beaucoup de composants, la maintenance peut devenir difficile car l’état complet de l’application se trouve éparpillé dans de multiple composants. C’est là que Vuex intervient.

Vuex est l’implémentation de Flux pour Vue. Flux est une méthode de structuration des données et des méthodes d’échanges entre composants.

Pour faire simple, Vuex gère un état commun pour l’intégralité de l’application, ainsi que le respect des méthodes de mises à jour. La mise à jour des données ne doit se faire qu’en respectant ce principe, le fameux one-way data flow.

Vuex pattern

Cette image en explique le principe complet de fonctionnement. Pour mettre à jour l’état de l’application, un composant doit :

  • appeller une action (dispatch) : cette action peut faire appel à un backend et peut être asynchrone
  • demander le changement d’état (commit) de manière synchrone (soit le composant soit l’action)
  • ce changement d’état modifie l’état global (mutate) de manière synchrone
  • ce changement provoque la mise à jour des composants dépendants, de manière asynchrone

Et ainsi de suite

Voici un exemple simplifié de l’utilisation de Vuex (ne pas oublier de charger la librairie)

<script src="https://unpkg.com/vuex"></script>
// le fameux état global, s’appelle un store
// contient:
//  - state: l’état
//  - mutations: les changements d’état possible

let store = Vuex.createStore({
  state() {
    return {
      count: 0,
    };
  },
  mutations: {
    increment: state => state.count++,
    decrement: state => state.count--,
  },
});

Vue.createApp({
  store: store,
  computed: {
    count () {
      return store.state.count;
    },
  },
  methods: {
    increment () {
      store.commit('increment');
    },
    decrement () {
      store.commit('decrement');
    },
  },
}).mount('#app');
<div id="app">
  <p>{{ count }}</p>
  <p>
    <button @click="increment">+</button>
    <button @click="decrement">&minus;</button>
  </p>
</div>

L’intérêt étant qu’un autre composant puisse interagir simplement avec ces données

app.component('five', {
  template: '<div>{{ isFive }}</div>',
  computed: {
    isFive () {
      return store.state.count === 5 ? 'Five!!!' : 'Not five';
    },
  },
});
<div id="app">
  ...
  <five/>
</div>

Voir cet exemple sur JSBin

Pour un confort d’utilisation de Vuex, il est recommandé d’utiliser un builder.