Comme pour Promises/A+, cet effort se concentre sur l'alignement de l'écosystème JavaScript. Si cet alignement est réussi, alors une norme pourrait émerger, basée sur cette expérience. Plusieurs auteurs de frameworks collaborent ici sur un modèle commun qui pourrait soutenir leur noyau de réactivité. Le projet actuel est basé sur les contributions des auteurs/mainteneurs d'Angular, Bubble, Ember, FAST, MobX, Preact, Qwik, RxJS, Solid, Starbeam, Svelte, Vue, Wiz, et bien d'autres encore...
Contrairement à Promises/A+, cette proposition n'essaie pas de trouver une API de surface commune pour les développeurs, mais plutôt la sémantique précise du graphe de signaux sous-jacent. Cette proposition inclut une API entièrement concrète, mais elle n'est pas destinée à la plupart des développeurs d'applications. Au contraire, l'API de signal proposée est mieux adaptée aux cadres de travail à construire au-dessus, fournissant une interopérabilité par le biais d'un graphe de signal commun et d'un mécanisme de suivi automatique.
L'objectif de cette proposition est de réaliser des prototypes préliminaires significatifs, y compris l'intégration dans plusieurs frameworks, avant de dépasser l'étape 1. Cette proposition ne s'intéresse pas par la normalisation des signaux que s'ils peuvent être utilisés en pratique dans plusieurs cadres et s'ils offrent de réels avantages par rapport aux signaux fournis par les cadres. Un prototypage précoce significatif permettra d'obtenir ces informations.
Contexte : Pourquoi des signaux ?
Pour développer une interface utilisateur (UI) complexe, les développeurs d'applications JavaScript doivent stocker, calculer, invalider, synchroniser et transmettre l'état à la couche de visualisation de l'application de manière efficace. Les interfaces utilisateur ne se limitent généralement pas à la gestion de simples valeurs, mais impliquent souvent le rendu d'un état calculé qui dépend d'un arbre complexe d'autres valeurs ou d'un état qui est lui-même calculé. L'objectif des signaux est de fournir une infrastructure pour gérer l'état de ces applications afin que les développeurs puissent se concentrer sur la logique commerciale plutôt que sur ces détails répétitifs.
Les constructions de type signal se sont révélées utiles dans des contextes autres que l'interface utilisateur, en particulier dans les systèmes de construction afin d'éviter les reconstructions inutiles.
Les signaux sont utilisés dans la programmation réactive pour supprimer la nécessité de gérer les mises à jour dans les applications.
Exemple - Un compteur VanillaJS
Avec une variable, counter, vous voulez rendre dans le DOM si le compteur est pair ou impair. Chaque fois que counter change, vous voulez mettre à jour le DOM avec la dernière parité. Dans Vanilla JS, vous pourriez avoir quelque chose comme ceci :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | let counter = 0; const setCounter = (value) => { counter = value; render(); }; const isEven = () => (counter & 1) == 0; const parity = () => isEven() ? "even" : "odd"; const render = () => element.innerText = parity(); // Simulate external updates to counter... setInterval(() => setCounter(counter + 1), 1000); |
Cela pose un certain nombre de problèmes...
- La configuration du counter est bruyante et lourde.
- L'état du counter est étroitement lié au système de rendu.
- Si le counter change mais que la parity ne change pas (par exemple, le compteur passe de 2 à 4), le calcul de la parité et le rendu sont inutiles.
- Que se passe-t-il si une autre partie de l'interface utilisateur souhaite simplement effectuer un rendu lorsque le counter est mis à jour ?
- Que se passe-t-il si une autre partie de l'interface utilisateur dépend de isEven ou de la parity uniquement ?
Même dans ce scénario relativement simple, un certain nombre de problèmes se posent rapidement. On pourrait essayer de les contourner en introduisant la fonction pub/sub pour le counter. Cela permettrait à d'autres consommateurs du counter de s'abonner pour ajouter leurs propres réactions aux changements d'état.
Cependant, on est toujours confrontés aux problèmes suivants :
- La fonction de rendu, qui ne dépend que de la parity, doit au contraire "savoir" qu'elle doit s'abonner au counter.
- Il n'est pas possible de mettre à jour l'interface utilisateur en se basant uniquement sur isEven ou la parity, sans interagir directement avec le counter.
- On a augmenté le code passe-partout. Chaque fois que vous utilisez quelque chose, il ne s'agit pas simplement d'appeler une fonction ou de lire une variable, mais plutôt de s'abonner et de faire des mises à jour à cet endroit. La gestion du désabonnement est également particulièrement compliquée.
On pourrait résoudre quelques problèmes en ajoutant pub/sub non seulement à counter mais aussi à isEven et parity. On devrait alors abonner isEven à counter, parity à isEven et render à parity. Malheureusement, non seulement le code de base a explosé, mais on est coincé avec une tonne de comptabilité d'abonnements, et un désastre potentiel de fuites de mémoire si on ne nettoit pas tout de la bonne manière. On a donc résolu certains problèmes, mais on a créé une toute nouvelle catégorie de problèmes et beaucoup de code. Pour aggraver les choses, on doit suivre tout ce processus pour chaque élément d'état du système.
Présentation des signaux
Les abstractions de liaison de données dans les interfaces utilisateur pour le modèle et la vue sont depuis longtemps au cœur des cadres d'interface utilisateur dans de nombreux langages de programmation, malgré l'absence d'un tel mécanisme intégré à JS ou à la plateforme web. Au sein des frameworks et des bibliothèques JS, de nombreuses expériences ont été menées sur les différentes manières de représenter cette liaison, et l'expérience a montré la puissance du flux de données unidirectionnel en conjonction avec un type de données de première classe représentant une cellule d'état ou de calcul dérivée d'autres données, désormais souvent appelée "signaux". Cette approche de valeur réactive de première classe semble avoir fait sa première apparition populaire dans les frameworks web JavaScript open-source avec Knockout en 2010. Depuis, de nombreuses variantes et implémentations ont été créées. Au cours des 3-4 dernières années, la primitive Signal et les approches connexes ont gagné en popularité, avec presque toutes les bibliothèques ou frameworks JavaScript modernes ayant quelque chose de similaire, sous un nom ou un autre.
Pour comprendre les signaux, reprenons l'exemple ci-dessus, réimaginé avec une API de signaux décrite plus en détail ci-dessous.
Exemple - Un compteur de signaux
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | const counter = new Signal.State(0); const isEven = new Signal.Computed(() => (counter.get() & 1) == 0); const parity = new Signal.Computed(() => isEven.get() ? "even" : "odd"); // A library or framework defines effects based on other Signal primitives declare function effect(cb: () => void): (() => void); effect(() => element.innerText = parity.get()); // Simulate external updates to counter... setInterval(() => counter.set(counter.get() + 1), 1000); |
- On a éliminé le code passe-partout bruyant autour de la variable counter de l'exemple précédent.
- Il existe une API unifiée pour gérer les valeurs, les calculs et les effets de bord.
- Il n'y a pas de problème de référence circulaire ou de dépendances inversées entre counter et render.
- Il n'y a pas de souscriptions manuelles, ni de besoin de comptabilité.
- Il existe un moyen de contrôler la programmation des effets secondaires.
Les signaux apportent bien plus que ce que l'on peut voir à la surface de l'API :
- Suivi automatique des dépendances - Un signal calculé découvre automatiquement tous les autres signaux dont il dépend, que ces signaux soient de simples valeurs ou d'autres calculs.
- Évaluation paresseuse - Les calculs ne sont pas évalués avec empressement lorsqu'ils sont déclarés, ni immédiatement lorsque leurs dépendances changent. Ils ne sont évalués que lorsque leur valeur est explicitement demandée.
- Mémorisation - Les signaux calculés mettent en cache leur dernière valeur afin que les calculs dont les dépendances ne changent pas n'aient pas besoin d'être réévalués, quel que soit le nombre de fois où l'on y accède.
Motivation pour la normalisation des signaux
Interopérabilité
Chaque implémentation de signal a son propre mécanisme de suivi automatique, pour garder une trace des sources rencontrées lors de l'évaluation d'un signal calculé. Il est donc difficile de partager des modèles, des composants et des bibliothèques entre différents frameworks - ils ont tendance à être faussement couplés à leur moteur de vue (étant donné que les signaux sont généralement mis en œuvre dans le cadre de frameworks JS).
L'un des objectifs de cette proposition est de découpler complètement le modèle réactif de la vue de rendu, permettant aux développeurs de migrer vers de nouvelles technologies de rendu sans réécrire leur code non-IU, ou de développer des modèles réactifs partagés en JS pour être déployés dans différents contextes. Malheureusement, en raison des versions et de la duplication, il s'est avéré impossible d'atteindre un niveau de partage élevé via les bibliothèques de niveau JS - les composants intégrés offrent une garantie de partage plus forte.
Performance/utilisation de la mémoire
Il est toujours possible d'améliorer légèrement les performances en livrant moins de code grâce à l'intégration de bibliothèques couramment utilisées, mais les implémentations de Signals sont généralement assez petites, et cet effet ne devrait pas être très important.
Les implémentations natives en C++ des structures de données et des algorithmes liés aux signaux peuvent être légèrement plus efficaces que ce qui est réalisable en JS, d'un facteur constant. Cependant, aucun changement algorithmique n'est prévu par rapport à ce qui serait présent dans un polyfill ; les moteurs ne devraient pas être magiques ici, et les algorithmes de réactivité eux-mêmes seront bien définis et sans ambiguïté.
Le groupe de champions prévoit de développer diverses implémentations des signaux et de les utiliser pour étudier ces possibilités de performance.
Outils de développement
Avec les bibliothèques de signaux existantes en langage JS, il peut être difficile de tracer des éléments tels que :
- La pile d'appels d'une chaîne de signaux calculés, montrant la chaîne de causalité d'une erreur.
- Le graphe de référence entre les signaux, lorsqu'un signal dépend d'un autre - important lors du débogage de l'utilisation de la mémoire.
Les signaux intégrés permettent aux moteurs d'exécution JS et aux outils de développement de disposer d'une meilleure prise en charge de l'inspection des signaux, en particulier pour le débogage ou l'analyse des performances, qu'elle soit intégrée dans les navigateurs ou qu'elle fasse l'objet d'une extension partagée. Les outils existants, tels que l'inspecteur d'éléments, l'instantané des performances et les profileurs de mémoire, pourraient être mis à jour pour mettre en évidence les signaux dans leur présentation des informations.
Avantages secondaires
- Avantages d'une bibliothèque standard
En général, JavaScript dispose d'une bibliothèque standard assez minimale, mais une tendance dans le TC39 a été de faire de JS un langage plus "inclus dans les batteries", avec un ensemble de fonctionnalités intégrées de haute qualité disponibles. Par exemple, Temporal remplace moment.js, et un certain nombre de petites fonctionnalités, comme Array.prototype.flat et Object.groupBy, remplacent de nombreux cas d'utilisation de lodash. Parmi les avantages, citons la réduction de la taille des paquets, l'amélioration de la stabilité et de la qualité, moins de choses à apprendre lorsque l'on rejoint un nouveau projet, et un vocabulaire généralement commun à tous les développeurs JS. - Intégration HTML/DOM (une possibilité future)
Les travaux actuels du W3C et des implémenteurs de navigateurs cherchent à apporter la modélisation native au HTML (DOM Parts et Template Instantiation). En outre, le groupe de travail du W3C sur les composants Web étudie la possibilité d'étendre les composants Web afin d'offrir une API HTML entièrement déclarative. Pour atteindre ces deux objectifs, le HTML aura besoin d'une primitive réactive. En outre, de nombreuses améliorations ergonomiques du DOM grâce à l'intégration des signaux peuvent être imaginées et ont été demandées par la communauté. - Échange d'informations sur l'écosystème (ce n'est pas une raison pour expédier)
Les efforts de normalisation peuvent parfois être utiles au niveau de la "communauté", même sans changements dans les navigateurs. Le projet des signaux (Signals) rassemble de nombreux auteurs de cadres différents pour une discussion approfondie sur la nature de la réactivité, des algorithmes et de l'interopérabilité. Cela s'est déjà avéré utile et ne justifie pas l'inclusion dans les moteurs JS et les navigateurs ; Signals ne devrait être ajouté à la norme JavaScript que s'il y a des avantages significatifs au-delà de l'échange d'informations de l'écosystème activé.
Source : JavaScript Signals standard proposal
Et vous ?
Pensez-vous que cette proposition est crédible ou pertinente ?
Quel est votre avis sur le sujet ?
Voir aussi :
Popularité des langages sur GitHub : Python, Go et JavaScript en progression, tandis que Java et C++ sont en légère baisse mais restent dans le Top 5, d'après GitHut 2.0
JavaScript est resté le langage de programmation le plus demandé de janvier 2022 à juin 2023, d'après DevJobsScanner, un emploi de développeur sur trois nécessite des connaissances en JavaScript
État de JavaScript 2022 : React reste le framework front-end dominant mais est en perte de vitesse côté satisfaction. JQuery est la troisième bibliothèque la plus utilisée