Go to Hackademy website

Rendering avancé avec Vue.js

Tom Panier

Posté par dans la catégorie 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

Introduction à React

21/04/2016

React n’est pas à proprement parler un framework mais se présente comme une bibliothèque JavaScript pour créer des interfaces utilisateurs. React est la réponse de Facebook à un problème récurrent : créer des interfaces réutilisables et stateful (je…

Résumé de la conférence Vue.js Amsterdam 2018

20/02/2018

Avec Victor et Gaëtan, nous avons assisté à la conférence Vue.js s’étant tenue à Amsterdam à la mi-février 2018. Vous trouverez dans ce billet notre retour sur les différentes interventions auxquelles nous avons pu assister et ce que nous avons r…

Des applications isomorphiques avec Nuxt.js

01/03/2018

Comme vous avez sûrement pu le constater, les articles traitant de Vue.js se multiplient sur ce blog. Cela s’explique par son adoption grandissante par nos équipes, qui apprécient sa simplicité et son écosystème florissant. Parmi les nombreux projet…

Des composants universels avec React et Vue.js

24/05/2018

Dans notre article traitant de rendering avancé avec Vue.js, nous avions exploré la possibilité, pour un composant écrit pour ce framework, de tirer parti de JSX, le langage de templating initialement prévu pour être utilisé avec React. Si vous êtes…

Afficher plus Afficher moins

Ajouter un commentaire