Go to Hackademy website

Rendering avancé avec Vue.js

Tom Panier

Posté par Tom Panier dans les catégories front

Lors de notre introduction en trois parties à Vue.js, nous avons vu ensemble comment construire une application simple, en tirant notamment parti du système de templating proposé par le framework, à base de balises et de directives telles que v-if et v-for.

Bien que simple et riche, cette façon de procéder peut-elle toutefois suffire dans tous les cas de figure ? Je vais laisser cette question en suspens et, dans l’intervalle, vous montrer ce que l’on peut faire d’autre pour définir le rendu de nos composants ;)

Love me render, love me true

Si vous avez déjà utilisé React, le concept d’un composant comprenant une méthode render ne vous est probablement pas étranger : c’est cette méthode que le framework appelle pour obtenir un rendu. Dans un SFC Vue.js, c’est la section template qui remplit cet office, en présentant directement le balisage correspondant.

L’inconvénient, c’est qu’on se retrouve vite limité si on a besoin de faire beaucoup varier le rendu en fonction de certains critères. Bien sûr, les directives dont je parlais en introduction sont là pour nous donner un peu de souplesse, mais imaginez un composant dont l’objectif est d’effectuer le rendu d’un champ de formulaire :

<TextInput
  :multiline="true /* ou false */"
  name="something"
  value="Coucou"
  @change="someMethod"
/>

Note : toute ressemblance avec l’API de React Native serait purement fortuite.

Selon la valeur de la prop multiline, ce composant sera ultimement traduit, dans un cas, par :

<textarea
  :name="name"
  @input="() => $emit('change')"
>{value}</textarea>

Et dans l’autre, par :

<input
  type="text"
  :name="name"
  :value="value"
  @input="() => $emit('change')"
/>

Alors, oui, on pourrait effectivement s’en sortir avec un v-if, mais au prix de devoir répéter une prop et un event handler, et en tant que bons développeurs, on n’aime pas trop ça. De plus, vous vous doutez que l’exemple pris ici est volontairement simple, mais qu’on pourrait avoir bien plus de duplication de code (props de validité HTML5, class voire style, etc.).

Alors, comment faire ? C’est très simple : on oublie la fameuse section template, et on ajoute à notre composant… une méthode render !

export default {
  props: {
    multiline: Boolean,
    name: String,
    value: String
  },

  render(createElement) {}
};

Heu, on fait comment ?

La méthode render reçoit en paramètre une fonction fournie par Vue.js, intitulée createElement, et qui fait bien ce que son nom dit qu’elle fait : créer programmatiquement un élément. Elle accepte elle-même jusqu’à trois paramètres :

  • le type de l’élément à créer, à savoir soit une chaîne contenant un nom de balise HTML (par exemple, "div"), soit une instance de composant Vue (par exemple, MyComponent), soit une fonction retournant l’un des deux — quand je vous disais que c’était souple !
  • un objet de paramétrage, permettant de… paramétrer l’élément à venir de manière plutôt complète — jugez-en plutôt par vous-même, au vu de la liste des clés possibles :
    • class
    • style
    • attrs
    • props
    • domProps
    • on
    • nativeOn
    • directives
    • scopedSlots
    • slot
    • key
    • ref
  • un tableau d’éléments (ou nœuds de texte) enfants, à créer eux aussi via la méthode createElement le cas échéant — s’il n’y en a qu’un, il est possible de le passer directement.

Tout cela doit vous paraître quelque peu indigeste de prime abord, notamment en ce qui concerne l’utilisation de l’objet de paramétrage, mais poursuivons si vous le voulez bien avec notre exemple, ce qui devrait commencer à lever le voile !

render(createElement) {
  const onInput = () => this.$emit("change");

  if (this.multiline) {
    return createElement("textarea", {
      attrs: {
        name: this.name
      },
      on: {
        input: onInput
      }
    }, this.value);
  }

  return createElement("input", {
    attrs: {
      type: "text",
      name: this.name,
      value: this.value
    },
    on: {
      input: onInput
    }
  });
}

Comme vous pouvez le voir, on procède finalement ici à un strict équivalent de ce qu’on aurait réalisé dans template, avec une syntaxe plus « bas niveau » mais infiniment plus souple, ce qui la rend plus adaptée à ce genre de cas de figure.

JSX (not so) Tricky

Il existe un outil qui peut nous permettre de conserver cette souplesse tout en gagnant en lisibilité : j’ai nommé JSX !

Bien connue elle aussi des utilisateurs de React, puisqu’elle a été introduite par ce dernier, il s’agit d’une autre syntaxe XML-like que celle des composants Vue, offrant davantage de liberté puisqu’on peut y interpoler à l’infini markup et code JavaScript.

Ce parti pris a ses avantages et ses inconvénients ; pour afficher conditionnellement un élément, par exemple, avec la syntaxe « habituelle », vous utiliseriez v-if :

<div v-if="someCondition">Hide and seek</div>

En JSX, point de directives, et point de structures de contrôle classiques non plus (typiquement, if) : il est nécessaire d’utiliser directement des expressions, et donc en l’occurrence de ruser avec une condition ternaire :

{someCondition ? <div>Hide and seek</div> : null}

Pour ce qui est des boucles, Vue utilise, vous le savez (n’est-ce pas ?), v-for :

<ul><li v-for="item in collection" :key="item.id">{item.name}</li></ul>

En JSX, il vous faudra utiliser Array.prototype.map, ou toute autre expression référençant un tableau ou autre itérable :

<ul>{collection.map(item => <li key={item.id}>{item.name}</li>)}</ul>

Il existe bien sûr d’autres différences, notamment la manière de passer une valeur dynamique à un attribut (:key="value" pour Vue, key={value} pour JSX) comme vous pouvez le voir ci-dessus.

Quoi qu’il en soit, sachez (si vous ne l’avez pas encore deviné) qu’il est possible d’utiliser JSX dans la méthode render d’un composant Vue !

render(h) { // renommer createElement en h est requis pour JSX
  const onInput = () => this.$emit("change");

  return this.multiline
    ? <textarea
      name={this.name}
      onInput={onInput}
    >{this.value}</textarea>
    : <input
      type="text"
      name={this.name}
      value={this.value}
      onInput={onInput}
    />;
}

Je vois pas trop l’intérêt, on répète de nouveau les props !

Tu as tout à fait raison : pour ce cas précis, l’intérêt d’utiliser JSX est discutable. Cela aura au moins eu le mérite de te le faire découvrir, d’autant plus qu’il est fort possible que nous en reparlions bientôt (mais c’est un secret).

C’est tout, pour le moment

Nous avons fait un bref tour d’horizon des possibilités de rendering avancé avec Vue.js ; je ne suis pas rentré dans les détails techniques de l’API par souci de brièveté, mais si vous souhaitez expérimenter sur le sujet, n’hésitez pas à consulter la page idoine de la documentation, très complète.

Je vous retrouve très prochainement avec un nouvel article sur Vue.js !


L’équipe Synbioz.
Libres d’être ensemble.

Articles connexes

Jeu de la vie et rendus de la mort

09/09/2019

Bonjour à tous, aujourd’hui je vous propose de revoir un classique du monde du développement, le jeu de la vie. Automate cellulaire plus qu’un vrai jeu, c’est avant tout un algorithme qui va nous...

Houdini, CSS by JS

21/03/2019

Bonjour à tous, bienvenue dans le monde magique de l’illusion et des faux-semblants, où un background peut souvent en cacher un autre. Que le rideau se lève le temps d’apercevoir ce qui se cache...

Architecture en trois tiers d'une application Vue.js

21/02/2019

Ça commence à faire un petit moment que je vous bassine avec Vue.js, et que je vous fais construire des single-page applications en s’appuyant dessus. Néanmoins, nous n’avons jamais réellement parlé...

Une bibliothèque pour gérer l'authentification avec Vue.js, partie 2 — en route vers HTTP

08/02/2019

À l’heure où j’écris ces lignes, il m’est difficile de prévoir le temps qu’il fera quand vous les lirez. Laissons donc cette fois-ci les considérations météorologiques de côté et replongeons-nous...