La réactivité est l'âme d'un framework web moderne. Quand les données changent, l'interface utilisateur devrait se mettre à jour. Cela semble simple. Ce ne l'est pas.
React le résout avec un DOM virtuel et un algorithme de diffing. Svelte le résout avec un suivi des dépendances au moment de la compilation. Vue le résout avec des proxies et un système d'effets réactifs. Angular le résout avec la détection de changements et les zones. Chaque approche a des compromis en complexité, performance et expérience développeur.
FLIN le résout différemment. La réactivité n'est pas une fonctionnalité de framework -- c'est une fonctionnalité de runtime, intégrée dans la VM et la couche serveur. Vous écrivez count = count + 1, et chaque {count} dans la vue se met à jour. Pas d'abonnements manuels. Pas de tableaux de dépendances. Pas de hooks useEffect.
Les trois couches de réactivité
Le système de réactivité de FLIN opère à trois couches :
- Rendu côté serveur : la VM exécute les opcodes de vues et produit du HTML avec des annotations réactives.
- Proxy côté client : un
ProxyJavaScript intercepte les affectations de variables et déclenche les mises à jour du DOM. - Mises à jour envoyées par le serveur : SSE pousse les changements de données du serveur au navigateur en temps réel.
Chaque couche gère un aspect différent de la réactivité. Ensemble, elles créent un système où les changements se propagent instantanément, qu'ils proviennent de l'interaction utilisateur (côté client), de la logique serveur (SSE) ou des mutations de base de données (abonnements aux entités).
Couche 1 : annotations réactives
Quand la VM rend une vue, elle marque le contenu dynamique avec des attributs data-flin-bind. Le texte statique est rendu directement. Les interpolations dynamiques ({name}, {count}) sont enveloppées dans des éléments <span> avec des attributs data-flin-bind. La valeur de l'attribut est l'expression à réévaluer quand l'état change.
Cette approche par annotations a deux avantages par rapport au diffing de DOM virtuel :
Précision. Le runtime sait exactement quels noeuds DOM doivent être mis à jour. Il n'a pas besoin de différer l'arbre entier -- il interroge [data-flin-bind] et met à jour seulement ces éléments.
Simplicité. La mise à jour réactive entière est une requête DOM plus une affectation de contenu texte. Pas de réconciliation d'arbre. Pas de planification de fibers. Pas de mode concurrent.
Couche 2 : le proxy réactif
Le runtime côté client enveloppe l'état de l'application dans un Proxy JavaScript :
javascriptconst _state = { count: 0, name: "Thales" };
const $flin = new Proxy(_state, {
set(target, property, value) {
target[property] = value;
_scheduleUpdate();
return true;
}
});Chaque variable d'état est exposée comme une propriété globale avec un getter et un setter. Quand un setter est appelé (par exemple count++ dans un gestionnaire d'événement), le piège set du Proxy se déclenche, met à jour l'état sous-jacent et planifie une mise à jour du DOM.
Couche 2.5 : batching des mises à jour
FLIN regroupe les mises à jour en utilisant requestAnimationFrame :
javascriptlet _updateScheduled = false;
function _scheduleUpdate() {
if (!_updateScheduled) {
_updateScheduled = true;
requestAnimationFrame(function() {
_flinUpdate();
_updateScheduled = false;
});
}
}
function _flinUpdate() {
document.querySelectorAll('[data-flin-bind]').forEach(function(el) {
const expr = el.getAttribute('data-flin-bind');
try {
const value = eval(expr);
if (el.textContent !== String(value)) {
el.textContent = value;
}
} catch (e) {
// L'évaluation de l'expression a échoué -- laisser le contenu inchangé
}
});
}Le drapeau _updateScheduled assure qu'un seul callback requestAnimationFrame est en file d'attente à la fois. Plusieurs changements d'état dans le même gestionnaire d'événement résultent en une seule mise à jour du DOM au prochain cadre d'animation.
La vérification if (el.textContent !== String(value)) évite les écritures DOM inutiles. Si la valeur n'a pas réellement changé, le noeud DOM n'est pas touché. Cela empêche le thrashing de mise en page et les repeintures inutiles.
Couche 3 : Server-Sent Events
La couche SSE gère la réactivité qui provient du serveur. Quand le serveur détecte un changement d'état (une mutation de base de données, une complétion de tâche de fond, un déclenchement de timer), il pousse la mise à jour au navigateur via la connexion SSE.
L'endpoint SSE (/_sse) sert deux objectifs dans FLIN :
- HMR : pousser des événements de rechargement quand les fichiers source changent.
- Mises à jour de données : pousser des notifications de changement d'entité quand la base de données est modifiée.
Le pipeline de réactivité
En mettant les trois couches ensemble, le pipeline de réactivité complet ressemble à ceci :
L'utilisateur clique sur un bouton
-> Le gestionnaire d'événement s'exécute (count++)
-> Le piège set du Proxy se déclenche
-> _scheduleUpdate() mis en file d'attente
-> requestAnimationFrame se déclenche
-> _flinUpdate() s'exécute
-> Tous les éléments [data-flin-bind] réévalués
-> Éléments changés mis à jour
Le serveur sauvegarde une entité
-> FlinDB notifie les observateurs d'entités
-> SSE diffuse un événement entity_change
-> Le client reçoit l'événement SSE
-> Le gestionnaire d'entité se déclenche
-> _flinUpdate() s'exécute
-> Éléments changés mis à jourTrois points d'entrée (interaction utilisateur, logique serveur, mises à jour distantes), un point de sortie (_flinUpdate). Cette convergence simplifie le débogage : si une liaison ne se met pas à jour, le problème est soit dans le point d'entrée (l'événement ne se déclenche pas), soit dans la fonction de mise à jour (l'expression ne s'évalue pas correctement). Il n'y a pas de graphe de dépendances complexe à tracer.
Le principe de conception
Le moteur de réactivité incarne le principe de conception fondamental de FLIN : rendre le cas courant automatique et le programmeur invisible.
Dans React, le développeur doit appeler useState, déstructurer l'état et le setter, appeler le setter pour déclencher un re-rendu, et gérer le tableau de dépendances de useEffect pour éviter les fermetures périmées. Dans FLIN, le développeur écrit count = 0 et {count}. Le système de réactivité gère tout le reste.
Ce n'est pas une préférence philosophique. C'est une décision pratique ancrée dans le public cible de FLIN : les développeurs en Cote d'Ivoire et à travers l'Afrique de l'Ouest qui veulent construire des applications web rapidement, sans maîtriser les subtilités des hooks React ou de la syntaxe $: de Svelte. Le système de réactivité devrait être invisible parce que le développeur a des choses plus importantes auxquelles penser -- comme l'application qu'il construit.
Ceci est la partie 28 de la série « Comment nous avons construit FLIN », documentant comment un CEO à Abidjan et un CTO IA ont construit un langage de programmation à partir de zéro.
Prochain : [29] Le premier rendu dans le navigateur -- le moment marquant où le code FLIN a compilé en HTML et est apparu dans Chrome pour la première fois.