L'après jQuery: API DOM native et alternatives
Ça ne vous a surement pas échappé, on utilise aujourd’hui de moins en moins jQuery dans les nouveaux projets. Les navigateurs récents supportent en effet très bien les nouvelles API du DOM. Il est ainsi possible d’avoir un code qui fonctionne pour tous les navigateurs en se conformant aux standards. On se libère d’une dépendance mais on hérite d’une API native pas toujours super sexy… Voyons ce que l’on peut faire.
Valeur de retour
La première chose qu’on perd au change, c’est la syntaxe très claire et cohérente de jQuery :
// en jQuery
$('.yeah').each(el => …);
// en natif
const yeah = document.getElementsByClassName('yeah');
[...yeah].forEach(el => …);
Vous remarquez tout de suite que la syntaxe native est plus longue, mais vous vous dites surtout WTF! quand vous lisez ça [...yeah]
.
Eh oui, getElementsByClassName
retourne une HTMLCollection
. Cet élément est une collection d’objets et non un tableau. À ce titre on peut le transformer en tableau avec Array.from
ou le spread operator comme nous l’avons fait.
Ensuite, seulement, nous pouvons itérer dessus. Sans ça, comme l’objet a une propriété length
, vous pouvez toujours faire un for
… pratique. Rassurez-vous getElementsByTagName
retourne aussi une HTMLCollection
, donc on a un peu de cohérence.
Cependant, ce n’est pas le cas de querySelectorAll
. Lui, son truc c’est la NodeList
. Cette dernière est un objet, mais il implémente la méthode forEach
, houra ! On reprend donc notre exemple précédent :
const yeah = document.querySelectorAll('.yeah');
yeah.forEach(el => …);
// évidemment, sans variable intermédiaire c'est possible aussi
document.querySelectorAll('.yeah').forEach(el => …);
Avant de vous enjailler et d’abandonner complètement getElementsByClassName
et getElementsByTagName
, il faut quand même bien comprendre la différence entre HTMLCollection
et NodeList
.
La première est live – c’est à dire qu’elle s’actualise en temps réel au gré des modifications du DOM – tandis que la seconde est statique.
// admettons que nous ayons 2 div.yeah
const yeahLive = document.getElementsByClassName('yeah');
const yeahStatic = document.querySelectorAll('.yeah');
yeahLive.length; // 2
yeahStatic.lenght; // 2
document.querySelector('body').insertAdjacentHTML('beforeend', '<div class="yeah"></div>');
yeahLive.length; // 3
yeahStatic.lenght; // 2
La plupart du temps, statique suffit, et c’est même parfois ce que l’on veut ! Mais sachez au moins quelle est la différence.
En outre, le dernier exemple illustre bien la lourdeur de l’API DOM. OMG, vous avez vu la longueur de ligne nécessaire à l’ajout d’un élément dans le DOM ? En jQuery, $(body).append('<div class="yeah"></div>')
aurait fait le boulot.
API DOM sur mesure
Nous l’avons vu, le principal problème de l’API DOM est sa syntaxe pour le moins verbeuse. Peut-être voulez-vous conserver la même syntaxe que jQuery ? Ou alors vous cherchez un style de code plus fonctionel ? Dans ce cas vous préféreriez probablement des appels à fonction plutôt que l’usage constant de méthodes depuis des objets du DOM (document
, window
, ou HTMLElement
).
Bref, tout est question de goût. Bien entendu, il n’y a qu’une seule API exposée par les navigateurs. Cela dit, rien n’empêche de créer des wrappers afin de faire votre propre API, c’est d’ailleurs bien ce qu’est jQuery.
Je ne vais pas vous apprendre à créer une fonction, donc un petit wrapper est tout aussi simple que :
function find(selector) {
return document.querySelectorAll(selector);
}
Cette approche a l’avantage de permettre de définir vous-même vos signatures et le nom des fonctions. Ainsi, vous pouvez préférer avoir l’élément DOM en dernier etc…
// Tout est question de goûts
function on(trigger, fn, el) {
return el.addEventListener(trigger, fn);
}
// ou
function listen(el, trigger, fn) {
return el.addEventListener(trigger, fn);
}
Ces deux fonctions font la même chose. Si vous créez votre propre bibliothèque, tâchez quand-même de vous astreindre à une certaine cohérence.
Les alternatives clef en main
Tout écrire vous-même peut s’apparenter à réinventer la roue tant il existe d’alternatives. Certaines reprennent jQuery en version plus light, avec une syntaxe égale ou proche ; d’autres se contentent de faciliter l’usage de l’API DOM avec une approche souvent plus fonctionnelle.
J’ai pour ma part développé une bibliothèque ultra légère. Elle met en avant un style fonctionnel et de ce fait, elle se marie fort bien avec Ramda (pour ceux qui en sont fan).
Elle a pour doux nom ƒlightDom – pour functional light DOM – et voici un petit exemple tiré de la doc.
import { find, findAll, addClass, on, onAll } from 'flightdom';
// sélection et ajout d'une classe au clic
const lightbox = find('.lightbox');
on(lightbox, 'click', () => addClass(lightbox, 'active'));
// la même chose avec une multitude d'éléments
const lightboxes = findAll('.lightbox');
onAll(lightboxes, 'click', e => addClass(e.target, 'active'));
Vous constatez qu’on adopte une approche assez fonctionnelle : on passe les objets du DOM a des fonctions plutôt que d’invoquer des méthodes sur ces objets. Cela facilite un style de programmation fonctionnel pour ceux qui le désirent.
N’hésitez pas à comparer les syntaxes, les fonctions (Zepto et Umbrella gèrent l’ajax par exemple) et surtout le poids ! À ce titre, un petit tour dans BundlePhobia ne fait jamais de mal.
À cela s’ajoute la compatibilité de la librairie avec le Tree Shaking (importer seulement ce qui est réellement utilisé) si vous utilisez un outil de build qui le supporte, tel que Rollup.
DOM traversal, mais pas que…
Nous avons jusque là surtout considéré jQuery comme un outil permettant de manipuler le DOM simplement. C’est à dire sélection, ajout, suppression et modification d’élements. Cependant, jQuery permet aussi de gérer l’ajax et les animations.
Dans les alternatives mentionnées ci-dessus, seule Zepto réuni l’ensemble des possibilités de jQuery. Les autres se contentent de la sélection. Pourtant, l’ajax et l’animation sont des besoins courants.
Pour l’ajax, plusieurs options s’offrent à nous : natif ou bibliothèque. L’option native présente deux possibilités, à savoir XMLHttpRequest qui est l’API historique et la nouvelle API Fetch. Cette dernière est plutôt bien supportée et possède un polyfill pour les quelques navigateurs ne la supportant pas.
De nouveau, côté syntaxe, il y a des questions de goûts. Dans l’ensemble, Fetch est assez bien pensée mais pour certains cas, son API peut devenir lourde également. Deux bibliothèques populaires permettent de travailler l’ajax :
- SuperAgent dont l’API est proche de celle de jQuery,
- axios apporte quelques options que Fetch n’a pas [en].
Les animations sont encore un autre sujet. Dans l’ensemble, c’est plutôt bien de ne pas utiliser jQuery pour cela car ça n’est pas performant. Beaucoup d’animations peuvent avantageusement être implémentées en CSS et déclenchées par un changement de classe. C’est la solution qui convient à 90% des interactions web classiques.
Pour les cas plus poussés, auquel jQuery ne répondait de toute façon pas non plus, il existe des outils spécialisés.
Conclusion
DOM brut, fait maison ou biliothèque, c’est une question de goût mais aussi d’utilité. Si on a besoin de toucher au DOM 2/3 fois seulement, on pourra supporter d’écrire document.querySelectorAll
. Sur un projet un peu plus important, ça peut vite devenir lourd.
En conclusion, je dirai que jQuery n’est pas mort. Il peut encore s’avérer utile dans certains cas, d’autant plus que les dernières versions ont bien maigries, surtout si on prend la version slim qui n’inclut pas l’ajax et les animations. Cependant, il est assez dommage de ne pas pouvoir importer seulement ce dont on se sert.
Commentaires
Rejoignez la discussion !