Les Proxies et la réactivité en JavaScript

Vous êtes-vous déjà demandé comment fonctionnaient les librairies JavaScript qui mettent à jour la page quand vous modifiez une variable directement sans utiliser un "setter" ? Elles utilisent probablement un Proxy JavaScript !

Dans cet article, nous allons d'abord expliquer ce qu'est un proxy JavaScript, puis nous allons créer un exemple de page réactive grâce à un proxy.

Qu'est-ce qu'un proxy en JavaScript ?

ECMAScript 2015 (ES6) a introduit un nouveau concept en JavaScript : les proxies.

Ces proxies servent à wrapper un objet JavaScript afin d'intercepter les opérations fondamentales qu'on pourrait appliquer sur l'objet. Ces opérations sont, par exemple, l'accès et la modification des propriétés d'un objet.

C'est un peu flou ? Tout va s'éclairer avec des exemples :

const proxy = new Proxy(target, handlers);

target est l'objet à wrapper et handlers sont les opérations fondamentales dont nous avons parlé plus haut.

Maintenant, essayons l'opération get, la fonction prend en paramètre 2 choses :

  • l'objet wrappé (target)

  • le nom de la propriété à laquelle on essaie d'accéder (property)

const proxy = new Proxy({age: 21}, { get(target, property) { console.log(Lecture de target.${property}); return target[property]; } }); console.log(proxy.age)

Affichera :

Lecture de target.age 21

Ensuite, l'opération set, elle prend en paramètre 3 choses :

  • l'objet wrappé (target)

  • le nom de la propriété à laquelle on essaie d'acceder (property)

  • la nouvelle valeur (value)

const proxy = new Proxy({age: 21}, { set(target, property, value) { target[property] = value + 1; } }); proxy.age = 41; console.log(proxy.age);

Affichera :

42

Il existe d'autres opérations que get et set mais je vous laisse les découvrir vous-même si vous en avez envie.

Création d'une page réactive grâce à un proxy

Nous allons faire une page qui affiche :

  • l'âge actuel de l'utilisateur

  • l'âge de l'utilisateur dans 10 ans, 20 ans et 30 ans

Nous allons faire en sorte que cette page se mette à jour dès que l'on change la variable "age".

Tout d'abord, on fait une page HTML basique et dans le body, on met :

<body> <p>Votre age :</p> <input id="ageInput" type="text"> <h2 id="yourAge"></h2> <h2 id="plusTenYears"></h2> <h2 id="plusTwentyYears"></h2> <h2 id="plusThirtyYears"></h2> </body>

Ensuite, on wrappe l'état de notre page dans dans un proxy :

window.ReactiveSystem = { state: new Proxy({ age: 0 }, {}) }

Puis, on ajoute le système qui permettra de lancer une fonction à chaque fois que l'on modifie une propriété de l'objet.

Pour cela, j'ai choisi d'avoir une Map qui lie le nom de la propriété avec la fonction qu'il faut appeler lorsque la propriété change :

window.ReactiveSystem.onChangeHandler = new Map();

On ajoute une fonction pour associer facilement une propriété avec une fonction :

window.ReactiveSystem.addOnChangeHandler = (property, handler) => { window.ReactiveSystem.onChangeHandler.set(property, handler); }

On ajoute une fonction pour lancer facilement la fonction "onChange" associée à une propriété :

window.ReactiveSystem.runOnChangeHandler = (property) => { const handler = window.ReactiveSystem.onChangeHandler.get(property); handler?.(window.ReactiveSystem.state[property]); }

Ensuite, on ajoute une fonction pour ajouter une nouvelle propriété réactive sur notre page :

window.ReactiveSystem.addState = (property, state, onChangeHandler) => { window.ReactiveSystem.state[property] = state; window.ReactiveSystem.addOnChangeHandler(property, onChangeHandler); // on lance directement le onChange après l'ajout de la propriété pour que le premier rendu soit correct window.ReactiveSystem.runOnChangeHandler(name); }

Enfin, on change le set de notre proxy pour qu'il appelle notre fonction runOnChangeHandler

window.ReactiveSystem.state = newProxy({}, { set(target,propertyName,value) { target[propertyName] = value; window.ReactiveSystem.runOnChangeHandler(propertyName); }, });

Maintenant que notre système est fini, il ne manque plus qu'à l'utiliser, pour cela, on crée une fonction qui va mettre à jour les choses liées à l'âge que l'utilisateur rentrera :

const renderAgeThings = (value) => { const rawAge = +value; const age = isNaN(rawAge) ? 0 : rawAge; const agePlusTen = Dans 10 ans vous aurez ${age+10} ans; const agePlusTwenty = Dans 20 ans vous aurez ${age+20} ans; const agePlusThirty = Dans 30 ans vous aurez ${age+30} ans; document.getElementById('yourAge').innerHTML = Vous avez ${age} ans; document.getElementById('plusTenYears').innerHTML = agePlusTen; document.getElementById('plusTwentyYears').innerHTML = agePlusTwenty; document.getElementById('plusThirtyYears').innerHTML = agePlusThirty; document.getElementById('ageInput').value = age.toString(); }

Enfin, il faut utiliser notre fonction addState :

window.ReactiveSystem.addState('age', 21, renderAgeThings);

Si on veut que l'âge change lorsque l'utilisateur modifie son âge avec le text input, il faudra rajouter :

document.getElementById('ageInput').addEventListener('input', (e) => { window.ReactiveSystem.state.age = e.target.value })

C'est fini ! Maintenant, si on ouvre la console JavaScript dans les outils de développement et que l'on modifie la propriété window.ReactiveSystem.age, le contenu de la page est mis à jour !

Voici les liens pour tester ou voir le code complet.

Vous souhaitez être accompagné pour lancer votre projet digital ?
Déposez votre projet dès maintenant
Architecture hexagonale : principes, bénéfices et conception
Le choix de l'architecture de votre application web ou mobile se fait en général en début de projet, lors de la phase ...
Arnaud Albalat
Arnaud Albalat
CTO @ Galadrim
Type Branding & Flavoring : Rendez votre code TypeScript plus lisible et plus robuste
Le système de types de TypeScript est structurel et c'est l'un de ses principaux avantages. Cette caractéristique ...
Matthieu Locussol
Matthieu Locussol
Full-Stack Developer @ Galadrim
Fonction fléchée vs fonction traditionnelle en JavaScript
Si en JavaScript vous vous êtes déjà demandé quand utiliser les fonctions fléchées et quand utiliser les fonctions traditionnelles, ...
Mayeul Le Monies de Sagazan
Mayeul Le Monies de Sagazan
Full-Stack Developer @ Galadrim