Article écrit par Tom Panier
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 !