Je vous propose aujourd'hui de parler des librairies front-end en JavaScript, pourquoi elles-sont utiles et pourquoi il y en a autant. Mais avant de se lancer dans l'exploration il est important d'identifier le problème qu'elles résolvent.
Lorsque l'on écrit du JavaScript côté navigateur c'est souvent pour créer des interfaces dynamiques qui réagissent en fonction des actions de l'utilisateur. On commence par créer la structure notre interface en manipulant le DOM.
root.innerHTML = `
<p>
<span>Compteur : <strong></strong></span>
</p>
<p>
<button class="btn btn-secondary">Incrémenter</button>
<button class="btn btn-secondary">Décrémenter</button>
</p>
`
Puis à l'aide du système d'évènement on va écouter les actions de l'utilisateur pour faire évoluer l'état de notre système.
count = 0
bindEvents() {
const buttons = this.root.querySelectorAll('button')
buttons[0].addEventListener('click', this.increment.bind(this))
buttons[1].addEventListener('click', this.decrement.bind(this))
}
increment () {
this.count++
}
decrement () {
this.count--
}
Enfin, il faut faire en sorte que notre interface reflète les changements de l'état.
updateCounterUI() {
if (this.count >= 10 && !this.alertElement) {
this.alertElement = document.createElement('div')
this.alertElement.classList.add('alert', 'alert-info')
this.alertElement.innerText = 'Ce chiffre est plus grand que 10'
this.root.append(this.alertElement)
}
if (this.count < 10 && this.alertElement) {
this.alertElement.remove()
this.alertElement = null;
}
this.countText.nodeValue = this.count.toString()
}
C'est ce dernier point qui peut rapidement grandir en complexité quand l'application va se développer. On se retrouve avec des mutations du DOM qui ne sont pas forcément simple à écrire et une structure qui devient difficile à maintenir.
Les librairies vont permettre de simplifier la création d'éléments interactifs en séparant la logique en 2 parties :
La librairie va s'occuper de faire le lien et de détecter quand l'état change et d'appliquer les changements au niveau du DOM pour créer une interface réactive.
Si on reprend l'exemple de notre compteur, voila à quoi le code pourra ressembler avec une de ces librairie.
<script>
let count = 0
const increment = () => {
count++
}
const decrement = () => {
count--
}
</script>
<p>
<span>Compteur avec librairie : <strong>{ count }</strong></span>
</p>
<p>
<button class="btn btn-secondary" on:click={increment}>Incrémenter</button>
<button class="btn btn-secondary" on:click={decrement}>Décrémenter</button>
</p>
{#if count >= 10}
<div class="alert alert-info">
Ce chiffre est plus grand que 10
</div>
{/if}
Dans la partie script on se focalise sur la logique de notre composant sans se soucier du DOM. Dans la partie template on définit, à l'aide d'une syntaxe spécifique, à quoi doit ressembler notre structure en fonction de l'état des différentes variables.
Maintenant que l'on est convaincu de l'utilité des librairies front-end on se retrouve devant un problème de taille, il en existe beaucoup ! Comme souvent dans le développement un problème a de multiple solutions.
La première problèmatique est la détection des changements de l'état de composant pour pouvoir, plus tard, mettre à jour l'interface. Il y a plusieurs solutions qui permettent d'arriver à gérer cette détection.
On retrouve cette approche dans react et preact. Le principe est d'avoir la valeur et un setter qui permettra de changer cette valeur.
function CounterComponent () {
const [count, setCount] = useState(3);
const increment = () => {
setCount(v => v + 1);
}
console.log("La valeur : " + count)
return //...
}
Lorsque le setter est créé, il est rattaché au contexte d'éxécution de la fonction, de sorte que la librairie sait le composant à rendre lorsque l'état est modifié. Cette approche apporte cependant plusieurs limitations :
Pour les plus curieux vous pouvez regarder cette vidéo où on crée notre propre version de react et des hooks.
Les signaux permettent de décorer une valeur dans un objet qui permettra ensuite d'observer les changements (la structure peut changer d'une librairie à l'autre).
const count = signal(0);
const increment = () => {
count.set(count() + 1)
}
// Si on veut la valeur directement
console.log("La valeur : " + count())
// Si on veut observer la valeur
count.subscribe(v => console.log('La valeur a changé', v))
Une autre approche consiste à utiliser l'étape de transpilation pour détecter les mutations à la volée.
let count = 0;
const increment = () => {
count++
}
console.log("La valeur : " + count)
Le compilateur va détecter à quel moment la variable est modifiée et rajouter la logique à la volée.
Le principe de cette approche est de patcher les méthodes des navigateurs pour savoir quand une interaction a lieu. On retrouve cette approche sur Angular.
Maintenant que l'on sait détecter quand l'état de notre composant change, il faut être capable d'appliquer ces changements au niveau du DOM. À ce niveau là il y a 2 grandes approches qui se distinguent.
Le principe de cette approche est d'introduire un niveau intermédiaire entre l'état des composants et le DOM. Ce niveau, appelé "VirtualDOM" va permettre de représenter notre structure sous forme d'arbre. Lors d'un changement d'état on va créer une nouvelle version de cet arbre et on le comparera à la version précédente afin de trouver les modifications à appliquer au DOM réel.
L'avantage de cette approche est que l'on n'est pas limité au DOM, on peut convertir les changement du VirtualDOM vers n'importe quoi. C'est ce qui permet par exemple à react d'être utilisé pour gérer l'interface d'une application mobile ou même la création de vidéos). L'inconvénient en revanche est que l'on rajoute une étape supplémentaire (génération du VirtualDOM et calcul des différences) qui à un coût supplémentaire en terme de calcul.
L'autre approche consiste à directement manipuler le DOM lorsque l'état change. Le problème est alors de trouver la manière la plus efficiente d'appliquer les transformations au niveau du DOM. Cette approche permet d'appliquer plus rapidement les changements au niveau du DOM mais il faudra faire attention à l'impact du comportement des navigateurs (pour éviter un re-paint non désiré par exemple).
Je ne vous ai présenté que quelques approches ici mais il existe beaucoup de variations possibles et on se retrouve avec un nombre assez important de combinaisons possibles pour résoudre notre problématique initiale. Et c'est ce qui explique le nombre important de librairies que l'on retrouve aujourd'hui. Mais comment choisir ?
Les différentes librairies ne correspondront pas forcément toutes aux mêmes cas d'utilisation. Si on veut juste créer un élément dynamique sur une page, on ne va pas utiliser un framework qui va proposer un grand nombre de fonctionnalités que l'on risque de ne pas utiliser et on va chercher à avoir une librairie avec le poid le plus léger possible. Par contre, si on veut créer une application entièrement piloté par le JavaScript on va prendre des librairies avec plus de fonctionnalités (router pour gérer l'URL, gestion des formulaires, gestion d'un état global...). Aussi, on ne regardera pas simplement la libairie mais son ecosytème pour aider dans ce choix.
La popularité n'est pas forcément un facteur en soi, mais cela a des implications importantes. D'abord pour l'apprentissage, une librairie qui a beaucoup d'utilisateurs, c'est une librairie sur laquelle on va facilement trouver des ressources pour apprendre et se perfectionner.
Ensuite, il y a la notion d'écosystème. Les librairies ont un cas d'usage qui est souvent limité à la céation d'éléments interactifs mais dès qu'il s'agit de faire autre chose on aura besoin de librairies supplémentaires (ajax, gestion de l'URL, formulaires...) et c'est là qu'un écosystème conséquent peut être un plus. Plus une librairie est populaire, plus il y a de gens qui travaillent avec et plus il y a de gens qui publient des outils que l'on va pouvoir utiliser.
Enfin, la popularité est aussi un facteur important pour l'emploi. C'est un facteur plus particulier, mais si vous cherchez à apprendre votre première librairie dans l'optique que ça vous serve dans votre recherche d'emploi, ça peut être intéressant de commencer par une librairie qui est très représentée. Dans ce cas-là, ce que je vous conseille de faire, c'est de rechercher les offres d'emploi dans votre région, regarder un petit peu ce qui est demandé, et prendre une technologie en fonction de ce que vous obtenez à ce niveau-là.
Au delà des différences techniques, les préférences personnelles vont aussi beaucoup influencer le choix d'une librairie en fonction de l'approche qui résonne le plus avec votre manière de réfléchir. Il y a certaines personnes chez qui l'approche React va plus résonner que l'approche de Vue, et inversement. Si vous voulez comparer la syntaxe / logique des librairies vous pourrez utiliser component.party qui permet de voir comment les différentes librairies approche les mêmes problèmes. Là il n'y a pas de secret, le mieux est de tester et de se faire un avis sur ce que l'on préfère utiliser.
Enfin, il faut savoir que toutes les librairies front-end ont une surface d'utilisation relativement restreinte et que l'apprentissage se fait assez rapidement. Aussi passer de l'une à l'autre se fait sans grande difficulté. Une fois les concepts de base acquis (surtout la notion d'état et de réactivité) il est facile de faire des analogies et la découverte d'une nouvelle librairie se fait beaucoup plus rapidement.