- Writing JavaScript Views the Hard Way : un article qui explique comment construire des vues en JavaScript pur, sans framework
- Une approche impérative directe permet d’assurer les performances, la maintenabilité et la portabilité
- Il sépare clairement les mises à jour d’état et les mises à jour du DOM, en suivant des conventions de nommage strictes et des modèles structurels selon chaque rôle
- Cette approche est facile à déboguer, garantit la compatibilité avec tous les navigateurs et offre le grand avantage de 0 dependencies
- Elle peut être difficile pour les débutants, mais apporte à l’apprentissage une compréhension profonde du fonctionnement réel des systèmes
Écrire des vues JavaScript à la « Hard Way »
De quoi s’agit-il ?
- Cette approche est un modèle pour construire des vues uniquement en JavaScript, sans framework comme React, Vue, lit-html
- Il s’agit du modèle de code lui-même, et non d’une bibliothèque ou d’un outil spécifique, ce qui évite les problèmes de code spaghetti
- En utilisant une approche impérative directe, elle réduit les abstractions et améliore l’intuitivité
Avantages par rapport aux frameworks
- Performances : grâce au code impératif, elle fonctionne sans calculs inutiles et convient aussi bien aux hot paths qu’aux cold paths
- 0 dependencies : pas de contraintes liées aux mises à jour de bibliothèques ou aux problèmes de compatibilité
- Portabilité : le code écrit peut être porté vers n’importe quel framework
- Maintenabilité : la structure claire en sections et les conventions de nommage facilitent la localisation du code
- Support navigateur : compatible avec la plupart des navigateurs à partir d’IE9, et même avec IE6 moyennant quelques adaptations
- Facilité de débogage : fournit des stack traces peu profondes sans couche intermédiaire
- Structure fonctionnelle : sans être immutable, l’ensemble des composants repose sur des fonctions
Explication de la structure
Structure générale
- Composée de
template → clone() → fonction init()
- La fonction
init() crée une instance de vue comprenant variables d’état, références DOM, fonctions de mise à jour, event listeners, etc.
Exemple de structure de code (Hello World)
const template = document.createElement('template');
template.innerHTML = `<div>Hello <span id="name">world</span>!</div>`;
function clone() {
return document.importNode(template.content, true);
}
function init() {
let frag = clone();
let nameNode = frag.querySelector('#name');
let name;
function setNameNode(value) {
nameNode.textContent = value;
}
function setName(value) {
if(name !== value) {
name = value;
setNameNode(value);
}
}
function update(data = {}) {
if(data.name) setName(data.name);
return frag;
}
return update;
}
Composition interne de la fonction init()
1. Variables DOM
frag est le fragment de template généré par clone()
- Les éléments internes sont référencés avec
querySelector(), et les noms de variables suivent la forme fooNode
2. Vues DOM
- Partie qui inclut d’autres vues (sous-vues réutilisables)
- Exemple :
let updateChildView = childView();
- Les fonctions de mise à jour de vue sont nommées sous la forme
updateFoo
3. Variables d’état
- Valeurs de données susceptibles d’être modifiées dans la vue
- Pour rendre les mises à jour du DOM efficaces, on compare avec la valeur courante et on ne modifie le DOM qu’en cas de besoin
4. Fonctions de mise à jour du DOM
- Utilisées pour modifier l’état des éléments du DOM
- Exemple :
function setNameNode(value) {
nameNode.textContent = value;
}
- Les manipulations du DOM doivent impérativement être effectuées uniquement dans ces fonctions
5. Fonctions de mise à jour d’état
- Elles incluent la logique de changement d’état et sa répercussion dans le DOM
- Les valeurs inchangées sont ignorées afin d’éviter les modifications inutiles du DOM
- Exemple :
function setName(value) {
if(name !== value) {
name = value;
setNameNode(value);
}
}
template et fonction clone()
template
- Création d’une structure HTML statique avec l’élément
<template>
- Il n’est pas inséré directement dans le DOM ; une copie est créée via clone
clone()
- Duplication via
document.importNode(template.content, true)
- Si nécessaire, il est possible de retourner l’élément racine avec
.firstElementChild
Mode d’interaction
Flux de données parent → enfant
- Le parent appelle
init() de l’enfant pour obtenir une fonction de mise à jour, puis l’appelle sous la forme update({ name: 'foo' })
Propagation des données par événements
- Suit fondamentalement le modèle props down, events up
- Les vues enfants communiquent en dispatchant des événements vers le niveau supérieur
Comparaison avec React
constructor() (React) → init() (Hard Way)
- Gère l’initialisation du composant
render() (React) → update(data) (Hard Way)
- Assure le rafraîchissement de l’affichage et la mise à jour de l’UI
this.setState() (React) → setX(value) (Hard Way)
- Remplacé par une méthode de définition directe de l’état
props (React) → valeurs transmises via update(data) (Hard Way)
- Manière de traiter les données transmises par le composant parent
- JSX / Virtual DOM (React) → template HTML + API DOM (Hard Way)
- Au lieu d’une UI déclarative, on utilise des manipulations manuelles du DOM et des templates
Conclusion
- Cette approche présente une barrière à l’entrée plus élevée que les frameworks habituels, mais offre les points forts suivants :
- Optimisation des performances
- Contrôle total
- Compréhension approfondie par l’apprentissage
- Grâce à la séparation des fonctions par rôle et aux conventions de nommage, il est possible de construire une UI maintenable sans framework
Compatibilité
- Les exemples récents utilisent des API destinées aux navigateurs modernes, mais le support peut être étendu jusqu’à IE9 et au-delà via des remplacements basés sur des fonctions
- En transmettant des fonctions via les props au lieu d’utiliser des événements, l’approche peut même être étendue jusqu’à IE6
3 commentaires
Au final, avec des composants web...
Félicitations. Un autre framework js vient de naître.
Avis Hacker News
Pour beaucoup de développeurs JS, ce sera peut-être hérétique, mais je pense que les variables de "state" sont un anti-pattern
value/textContent/checkeddes éléments DOM, etc., comme unique source de véritéLa documentation explique que cette approche est très maintenable, mais je ne suis pas d'accord
J'écris récemment des applications en TypeScript "vanilla" pur avec vite, et je remets de plus en plus en question les "meilleures" pratiques du frontend
Cette approche me rappelle l'ancienne bibliothèque backbone js
J'ai récemment imaginé quelque chose de similaire, mais sans utiliser d'éléments template
innerHTMLd'un élément existant ou dans un nouvel élémentdivCe code ressemble exactement au code de mise à jour manuelle que les bibliothèques de vues réactives cherchent à remplacer
Cela fait environ 20 ans que je programme, mais je ne me suis jamais habitué aux frameworks frontend
J'utilise un helper similaire à
React.createElementJe travaille sur deja-vu.junglecoder.com pour tenter de construire une boîte à outils JS pour des outils basés sur HTML
grab/patchest plutôt pas malDans mon premier emploi officiel après l'université, j'ai travaillé sur une version web d'un logiciel Delphi