Ce que les frameworks Web résolvent et comment s'en passer (Partie 1)
Publié: 2022-03-10Je suis récemment devenu très intéressé par la comparaison des frameworks avec le JavaScript vanille. Cela a commencé après une certaine frustration que j'ai eue en utilisant React dans certains de mes projets indépendants, et avec ma connaissance récente et plus intime des normes Web en tant qu'éditeur de spécifications.
J'étais intéressé de voir quels sont les points communs et les différences entre les frameworks , ce que la plate-forme Web a à offrir comme alternative allégée, et si c'est suffisant. Mon objectif n'est pas de dénigrer les cadres, mais plutôt de comprendre les coûts et les avantages, de déterminer si une alternative existe et de voir si nous pouvons en tirer des leçons, même si nous décidons d'utiliser un cadre.
Dans cette première partie, je vais approfondir quelques fonctionnalités techniques communes à tous les frameworks et comment certains des différents frameworks les implémentent. Je vais également examiner le coût d'utilisation de ces cadres.
Les cadres
J'ai choisi quatre frameworks à examiner : React, qui est le plus dominant aujourd'hui, et trois nouveaux concurrents qui prétendent faire les choses différemment de React.
- Réagir
« React facilite la création d'interfaces utilisateur interactives. Les vues déclaratives rendent votre code plus prévisible et plus facile à déboguer. - SolidJS
"Solid suit la même philosophie que React... Il a cependant une implémentation complètement différente qui renonce à l'utilisation d'un DOM virtuel." - Svelte
« Svelte est une nouvelle approche radicale de la création d'interfaces utilisateur… une étape de compilation qui se produit lorsque vous créez votre application. Au lieu d'utiliser des techniques telles que la différenciation virtuelle du DOM, Svelte écrit du code qui met à jour chirurgicalement le DOM lorsque l'état de votre application change. - Allumé
"S'appuyant sur les normes des composants Web, Lit ajoute juste... de la réactivité, des modèles déclaratifs et une poignée de fonctionnalités bien pensées."
Pour résumer ce que disent les frameworks sur leurs différenciateurs :
- React facilite la création d'interfaces utilisateur avec des vues déclaratives.
- SolidJS suit la philosophie de React mais utilise une technique différente.
- Svelte utilise une approche au moment de la compilation des interfaces utilisateur.
- Lit utilise les normes existantes, avec quelques fonctionnalités légères ajoutées.
Ce que les frameworks résolvent
Les frameworks eux-mêmes mentionnent les mots déclaratif, réactivité et DOM virtuel. Plongeons dans ce que cela signifie.
Programmation déclarative
La programmation déclarative est un paradigme dans lequel la logique est définie sans spécifier le flux de contrôle. Nous décrivons ce que le résultat doit être, plutôt que les étapes qui nous y mèneraient.
Aux débuts des frameworks déclaratifs, vers 2010, les API DOM étaient beaucoup plus simples et détaillées, et l'écriture d'applications Web avec JavaScript impératif nécessitait beaucoup de code passe-partout. C'est alors que le concept de "modèle-vue-vue-modèle" (MVVM) est devenu répandu, avec les frameworks alors révolutionnaires Knockout et AngularJS, fournissant une couche déclarative JavaScript qui gérait cette complexité à l'intérieur de la bibliothèque.
MVVM n'est pas un terme largement utilisé aujourd'hui, et c'est en quelque sorte une variation de l'ancien terme "liaison de données".
Liaison de données
La liaison de données est une manière déclarative d'exprimer comment les données sont synchronisées entre un modèle et une interface utilisateur.
Tous les frameworks d'interface utilisateur populaires fournissent une forme de liaison de données, et leurs didacticiels commencent par un exemple de liaison de données.
Voici la liaison de données dans JSX (SolidJS et React):
function HelloWorld() { const name = "Solid or React"; return ( <div>Hello {name}!</div> ) }
Liaison de données dans Lit :
class HelloWorld extends LitElement { @property() name = 'lit'; render() { return html`<p>Hello ${this.name}!</p>`; } }
Liaison de données dans Svelte :
<script> let name = 'world'; </script> <h1>Hello {name}!</h1>
Réactivité
La réactivité est une manière déclarative d'exprimer la propagation du changement.
Lorsque nous avons un moyen d'exprimer de manière déclarative la liaison de données, nous avons besoin d'un moyen efficace pour que le framework propage les changements.
Le moteur React compare le résultat du rendu avec le résultat précédent et applique la différence au DOM lui-même. Cette façon de gérer la propagation des changements est appelée le DOM virtuel.
Dans SolidJS, cela se fait plus explicitement, avec son magasin et ses éléments intégrés. Par exemple, l'élément Show
garderait une trace de ce qui a changé en interne, au lieu du DOM virtuel.
Dans Svelte, le code "réactif" est généré. Svelte sait quels événements peuvent provoquer un changement et génère un code simple qui trace la ligne entre l'événement et le changement DOM.
Dans Lit, la réactivité est accomplie à l'aide des propriétés des éléments, reposant essentiellement sur la réactivité intégrée des éléments HTML personnalisés.
Logique
Lorsqu'un framework fournit une interface déclarative pour la liaison de données, avec son implémentation de la réactivité, il doit également fournir un moyen d'exprimer une partie de la logique qui est traditionnellement écrite de manière impérative. Les blocs de construction de base de la logique sont « si » et « pour », et tous les principaux cadres fournissent une certaine expression de ces blocs de construction.
Conditionnels
Outre les données de base obligatoires telles que les nombres et les chaînes, chaque framework fournit une primitive "conditionnelle". Dans React, cela ressemble à ceci :
const [hasError, setHasError] = useState(false); return hasError ? <label>Message</label> : null; … setHasError(true);
SolidJS fournit un composant conditionnel intégré, Show
:
<Show when={state.error}> <label>Message</label> </Show>
Svelte fournit la directive #if
:
{#if state.error} <label>Message</label> {/if}
Dans Lit, vous utiliseriez une opération ternaire explicite dans la fonction render
:
render() { return this.error ? html`<label>Message</label>`: null; }
Listes
L'autre primitive commune du framework est la gestion de liste. Les listes sont un élément clé des interfaces utilisateur - liste de contacts, notifications, etc. - et pour fonctionner efficacement, elles doivent être réactives et ne pas mettre à jour toute la liste lorsqu'un élément de données change.
Dans React, la gestion des listes ressemble à ceci :
contacts.map((contact, index) => <li key={index}> {contact.name} </li>)
React utilise l'attribut key
spécial pour différencier les éléments de la liste et s'assure que toute la liste n'est pas remplacée à chaque rendu.
Dans SolidJS, les éléments intégrés for
et index
sont utilisés :
<For each={state.contacts}> {contact => <DIV>{contact.name}</DIV> } </For>
En interne, SolidJS utilise son propre magasin en conjonction avec for
et index
pour décider quels éléments mettre à jour lorsque les éléments changent. C'est plus explicite que React, ce qui nous permet d'éviter la complexité du DOM virtuel.
Svelte utilise la directive each
, qui est transpilée en fonction de ses mises à jour :
{#each contacts as contact} <div>{contact.name}</div> {/each}
Lit fournit une fonction de repeat
, qui fonctionne de manière similaire au mappage de liste basé sur les key
de React :
repeat(contacts, contact => contact.id, (contact, index) => html`<div>${contact.name}</div>`
Modèle de composant
Une chose qui sort du cadre de cet article est le modèle de composant dans les différents frameworks et la manière dont il peut être traité à l'aide d'éléments HTML personnalisés.
Note : C'est un gros sujet, et j'espère le traiter dans un prochain article car celui-ci serait trop long. :)
Le coût
Les frameworks fournissent une liaison de données déclarative, des primitives de flux de contrôle (conditions et listes) et un mécanisme réactif pour propager les modifications.
Ils fournissent également d'autres choses importantes, comme un moyen de réutiliser les composants, mais c'est un sujet pour un article séparé.
Les frameworks sont-ils utiles ? Oui. Ils nous offrent toutes ces fonctionnalités pratiques. Mais est-ce la bonne question à se poser ? L'utilisation d'un framework a un coût. Voyons quels sont ces coûts.
Taille du paquet
Lorsque je regarde la taille du paquet, j'aime regarder la taille réduite non Gzip. C'est la taille la plus pertinente pour le coût CPU de l'exécution de JavaScript.
- ReactDOM fait environ 120 Ko.
- SolidJS fait environ 18 Ko.
- Lit est d'environ 16 Ko.
- Svelte fait environ 2 Ko, mais la taille du code généré varie.
Il semble que les frameworks d'aujourd'hui fassent un meilleur travail que React pour garder la taille du bundle petite. Le DOM virtuel nécessite beaucoup de JavaScript.
Construit
D'une manière ou d'une autre, nous nous sommes habitués à «construire» nos applications Web. Il est impossible de démarrer un projet frontal sans configurer Node.js et un bundler tel que Webpack, gérer certains changements de configuration récents dans le pack de démarrage Babel-TypeScript, et tout ce jazz.
Plus la taille du bundle du framework est expressive et petite, plus la charge des outils de construction et du temps de transpilation est importante.
Svelte affirme que le DOM virtuel est une pure surcharge. Je suis d'accord, mais peut-être que la "construction" (comme avec Svelte et SolidJS) et les moteurs de modèles personnalisés côté client (comme avec Lit) sont également de purs frais généraux, d'un type différent ?
Débogage
Avec la construction et la transpilation, les coûts sont différents.
Le code que nous voyons lorsque nous utilisons ou déboguons l'application Web est totalement différent de ce que nous avons écrit. Nous nous appuyons désormais sur des outils de débogage spéciaux de qualité variable pour désosser ce qui se passe sur le site Web et le relier aux bogues de notre propre code.
Dans React, la pile d'appels n'est jamais "la vôtre" - React gère la planification pour vous. Cela fonctionne très bien lorsqu'il n'y a pas de bugs. Mais essayez d'identifier la cause des re-rendus en boucle infinie et vous serez dans un monde de douleur.
Dans Svelte, la taille du bundle de la bibliothèque elle-même est petite, mais vous allez expédier et déboguer tout un tas de code généré crypté qui est l'implémentation de Svelte de la réactivité, adaptée aux besoins de votre application.
Avec Lit, il s'agit moins de construire, mais pour le déboguer efficacement, vous devez comprendre son moteur de template. C'est peut-être la principale raison pour laquelle mon sentiment envers les frameworks est sceptique.
Lorsque vous recherchez des solutions déclaratives personnalisées, vous vous retrouvez avec un débogage impératif plus pénible. Les exemples de ce document utilisent Typescript pour la spécification de l'API, mais le code lui-même ne nécessite pas de transpilation.
Mises à jour
Dans ce document, j'ai examiné quatre frameworks, mais il y en a plus que je ne peux en compter (AngularJS, Ember.js et Vue.js, pour n'en nommer que quelques-uns). Pouvez-vous compter sur le framework, ses développeurs, son esprit et son écosystème pour travailler pour vous au fur et à mesure de son évolution ?
Une chose qui est plus frustrante que de corriger vos propres bogues est de devoir trouver des solutions de contournement pour les bogues du framework. Et une chose qui est plus frustrante que les bogues du framework sont les bogues qui se produisent lorsque vous mettez à niveau un framework vers une nouvelle version sans modifier votre code.
Certes, ce problème existe également dans les navigateurs, mais lorsqu'il se produit, il arrive à tout le monde et, dans la plupart des cas, un correctif ou une solution de contournement publiée est imminente. De plus, la plupart des modèles de ce document sont basés sur des API de plate-forme Web matures ; il n'est pas toujours nécessaire d'aller avec le bord saignant.
Sommaire
Nous avons approfondi un peu la compréhension des principaux problèmes que les frameworks tentent de résoudre et comment ils s'y prennent pour les résoudre, en nous concentrant sur la liaison de données, la réactivité, les conditions et les listes. Nous avons également examiné le coût.
Dans la partie 2, nous verrons comment ces problèmes peuvent être résolus sans utiliser de cadre du tout, et ce que nous pouvons en apprendre. Restez à l'écoute!
Remerciements particuliers aux personnes suivantes pour les révisions techniques : Yehonatan Daniv, Tom Bigelajzen, Benjamin Greenbaum, Nick Ribal et Louis Lazaris.