Vraie programmation réactive en JavaScript

Sans framework, on peut programmer par formules comme avec un tableur, grâce au dynamisme de JavaScript.

La programmation réactive est le troisième niveau dans la programmation de haut niveau: Le premier niveau a été atteint avec l'apparition de langages comme Fortran et Basic, puis en 1967 Simula nous a fait passer à un second niveau avec les objets, implémentés maintenant dans tous les languages. Le moment est sans doute venu d'accéder à un nouveau palier.

Car la PR est plus proche de la façon de penser de l'homme que la programmation impérative (type recette de cuisine), et je ne parle pas de la programmation fonctionnelle!
Un exemple. Si je veux évaluer la distance que je peux parcourir avec différent modèles de voiture, j'écrirais:

distance = capacité réservoir / consommation * 100

Ensuite tout ce que j'ai à faire est entrer la contenance du réservoir et la consommation de différents modèles de voiture et j'aurais toujours leur autonomie. Aucune ligne de code supplémentaire n'est nécessaire, pas plus que dans un tableur, la première application de la PR.

La PR convient plus particulièrement pour programmer un robot, une simulation, ou donner une intelligence à un jeu. Les résultats, comme la distance ci-dessus, peuvent être affichés par une interface ou peuvent être des commandes pour un appareil réel ou un objet virtuel.

Il existe plusieurs frameworks qui prétendent ajouter la programmation réactive à JavaScript, mais il n'est pas évident selon les tests que j'ai fait, qu'ils puissent recalculer une formule assignée à une variable. Ils se bornent juste à propager les évènements, c'est donc plutôt ce que l'on appelle la programmation par évènements. Le script ci-dessous permet à un programme de fonctionner comme un tableur, toutes les formules sont recalculées quand un paramètre change. C'est pourquoi j'ai ajouté "vrai" à coté de "programmation réactive" dans le titre.

Ayant développé le même script en PHP après la version JavaScript, j'ai apprécié comme il m'a été possible d'implémenter si simplement ce paradigme en JS, grâce au dynamisme du langage.
En JavaScript je l'implémente avec un objet qui tient en 20 lignes de code...

var Reactol = (function() {
Reactol.prototype.add = function(x) {
this.depend.push(x)
}
Reactol.prototype.change = function(x) {
if(x != undefined) this.value = x for(var i in this.depend) {
var d = this.depend[i]
d.action()
d.change() d.output()
}
}
function Reactol() {
this.depend=[]
this.value=0
}
return Reactol;
})();

Comme on le voit, j'utilise une closure pour ajouter un constructeur à l'objet Reactol.

Le tableau depend contient la liste des objets qui dépendent de l'objet courant. La méthode add permet d'ajouter une dépendance.

Quand on veut changer la valeur de l'objet, on appelle la méthode change laquelle à son tour appelle les méthodes action, change et output de chaque objet qui dépend de celui-ci.

Déclaration d'une variable réactive

Une variable réactive est représentée par un objet Reactol, on déclare donc d'abord une instance de l'objet:

var v1 = new Reactol()

Puis on définit la formule qui doit être assignée à cette variable:

v1['action'] = function() { this.value = 10  }

Eventuellement on ajoute une méthode pour afficher le contenu de la variable. Ce pourrait aussi être une commande pour un mécanisme.

v1['output'] = function() { document.getElementById("sum").value = this.value }

Maintenant on crée une dépendance, on définit une autre variable v2 qui dépend de la variable v1.

var v2 = new Reactol()
v2['action'] = function() { this.value = v1.value + 1000 }
v1.add(v2) 

On doit appeler la méthode add de v1 pour ajouter v2 à la liste des variables qui en dépendent. Par la suite, chaque modification de v1 entraînera une mise à jour automatique de v2.

Rien de plus n'est nécessaire pour rendre nos variables réactives!

Exemple dans une page HTML

Nous avons deux variables v1 et v2 qui sont liés à des champs de texte dans une page HTML.

Nous voulons définir une nouvelle variable sum dont la valeur est la somme de v1 et v2, et qui sera affichée aussi dans un champ de texte. Ainsi chaque fois que l'utilisateur modifiera la valeur de v1 ou v2, sum sera mise à jour automatiquement.

Voici le code JavaScript et HTML:

var Reactol = (function() {
Reactol.prototype.add = function(x) {
this.depend.push(x)
}
Reactol.prototype.change = function(x) { if(x != undefined) this.value = x
for(var i in this.depend) {
var d = this.depend[i]
d.action()
d.change()
d.output()
}
}
function Reactol() {
this.depend=[]
this.value=0
}
return Reactol
})();
var v1 = new Reactol()
var v2 = new Reactol()

var sum = new Reactol()
sum["action"] = function() { this.value = parseInt(v1.value) + parseInt(v2.value) }
sum["output"] = function() { document.getElementById("sum").value = this.value } v1.add(sum)
v2.add(sum)
<p>Value 1 : <input type="text" name="v1" onKeyUp="v1.change(this.value)"> </p>
<p>Value 2 : <input type="text" name="v2" onKeyUp="v2.change(this.value)"> </p>
<p>Sum : <input type="text" name="sum" id="sum"> </p>

Cet exemple simpliste sert uniquement à montrer le fonctionnement de l'objet Reactol. On pourrait obtenir le même effet sans cet objet, bien sûr, mais s'il y avait cinquante widgets dépendant les uns des autres ce serait plus difficile à gérer et dans ce cas, la programmation réactive prend tout son intérêt.

Démonstration

Value 1 :

Value 2 :

Sum :

Télécharger l'exemple complet. Le script est sous licence MIT. Utilisez le librement mais ne le présentez pas sur une page Web sans créditer l'auteur.

La programmation réactive est implémentée dans le compilateur Scriptol JavaScript (réalisé par moi-même), en utilisant l'objet décrit ici. Il suffit d'énoncer les formules, le compilateur se charge de déclarer automatiquement les dépendances.

Par Denis Sureau, le 18 septembre 2014.