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
Next.js App Router : le cache et ses dangers
“Il y a seulement 2 problèmes compliqués en informatique : nommer les choses, et l’invalidation de cache”. Phil Karlton. Avec ...
Valentin Gerest
Valentin Gerest
Full-Stack Developer @ Galadrim
Qu'est-ce que le SMS pumping et comment s'en protéger ?
La fraude appelée SMS pumping survient lorsque des fraudeurs exploitent un champ de saisie de numéro de téléphone de votre ...
Arnaud Albalat
Arnaud Albalat
CTO @ Galadrim
Les Normes RGAA et l’accessibilité numérique
En France, 90 % de la population, parmi lesquels se trouvent vos clients, se connectent à des sites web et des contenus en ...
Arnaud Albalat
Arnaud Albalat
CTO @ Galadrim