Le State management pour les webapps avec Redux
Introduction
Problématique
Dans une web app simple, par défaut on va permettre à nos composants de consommer de la donnée : un composant peut lire, créer ou éditer une donnée “A” , tandis qu’un autre peut lui aussi modifier cette donnée “A”, et qu’un troisième composant va pouvoir éditer une propriété de la donnée “A”. Il est facile d’avoir la référence d’un objet distribué à droite à gauche et de permettre un accès vers de multiples composants.
Aïe, le problème avec ce genre de process est qu’il se schématise par une grande toile d’araignée d’accès et de modification de donnée un peu partout, chaque cas totalement dépendant du contexte, ce qui devient très dur à suivre et à debugger …
State management
C’est là qu’intervient la notion de State Management. Pour le moment on peut considérer qu’un State représente plusieurs éléments :
- Une donnée initiale A
- Une réponse http
- Un input utilisateur
- Une navigation
- … etc.
L’idée de base est de pouvoir représenter l’état d’une application en un seul endroit afin d’obtenir un data flow one way. On peut voir le comportement problématique et la solution que nous souhaitons apporter par le schema suivant :
Pour se faire on va voir l’architecture proposée par Redux.
Qu’est ce que Redux
Redux est une simple librairie Javascript , agnostique de tout framework SPA ou autre ( bien que couramment utilisé dans un environnent React ). Il met à disposition un pattern de gestion d’état via plusieurs éléments représentant un data flow oneway cyclique.
Les principes
Voici les 3 principes de Redux :
- Single source of truth: l’état de l’application globale est stocké dans un “store” unique.
- State readonly : la notion d’Immutabilité est importante, en gros, une fois qu’un state est créé, il ne peut pas être modifié, il faut en recréer un pour apporter une évolution.
- Changes are made with pure functions: des fonctions seront dédiées aux modifications à apporter au State de l’application et celles-ci doivent être dites “pures”. Pour rappel le terme de fonction pure designe une fonction qui :
- N’a aucun effet de bord, elle ne modifie aucune autre donnée que sa valeur de retour.
- Ne prend en compte que ses paramètres d’entrée, sans notion d’état extérieur, elle peut etre executée n fois avec les mêmes paramètres et retournera toujours la même valeur.
Ces fonctions sont apppelées “Reducers”.
Dataflow
Maintenant que les principes de base sont posés, nous allons voir les éléments qui composent le flux de donnée de notre pattern suivi d’un exemple concret:
- Un State tree: l’état de notre application, qui a été créée / composée par un reducer
- Des Dispatched Actions: une action est un objet composé d’un type (identifiant : string, enum …etc) et d’un payload représentant une donnée.
- Des Reducers: fonction pure, elle reçoit une dispatched action, répondant à un type particulier, le réducer va composer un nouveau State en ayant pris en compte le payload de l’action
Tout ceci représente notre Store, qui est la brique de gestion d’état et qui peut être représentée comme suit :
Pour bien comprendre le flux, un composant A représentant un formulaire HTML, souhaite soumettre un champ texte dont la valeur doit s’afficher à l’écran.
La soumission du formulaire va donc appeler un dispatcher en lui précisant une action dont le type est: “ADD_TEXT” et le paylod : “Lorem Impsum”.
Le dispatcher, lui, va contacter le réducer correspondant au type d’action et lui demander de mettre à jour l’état de notre application. Le reducer retournera donc un nouveau State qui contiendra la valeur à notre composant initial.
Exemple simpliste d’implémentation
Postulat
la vue:
|
|
le code behind
|
|
Dans cet exemple, nous avons simplement un formulaire, et lors du clic sur le bouton de validation, nous voulons afficher la nouvelle entrée dans une liste, le tableau “results” est accessible par tous, et peut être modifié à n’importequel moment, voyons donc comment faire en sorte qu’il soit représenté dans un State.
Rajout du Store
Nous allons deja créer un objet “store” qui contiendra nos differents élément, d’abord un state:
1 2 3 4 5
var store = function () { var state = { persons: [] }; }()
Un reducer:
|
|
- Et enfin un dispatcher:
1 2 3
function dispatch(action) { state = reducer(action); }
Il ne reste plus qu’à dispatcher une action, voici l’évolution de notre handler pour l’évenement click:
|
|
Ok, à ce stade nous informons le store qu’une nouvelle valeur s’ajoute à notre tableau “persons” et que l’on doit donc faire évoluer le State. Cependant comment récupérer le nouveau State depuis un composant ? Nous allons ajouter des souscription !
Subscribe & Notify
Rajoutons donc à notre store des subscribers, une methode de subscribe, et une de notify comme suit:
|
|
Maintenant nous touchons au but, mais attention,
|
|
Maintenant n’importequel composant peut s’abonner et se refresh à la moindre modification de donnée. Code complet ici ==> https://jsfiddle.net/mv9jads1/
Quelle solution
Il existe plusieurs librairies JS, la plupart basées sur Redux, sensiblement similaires plus ou moins adaptées en fonction du projet comme par exemple :
- Conventional-redux
- MobX
- Flux (par Facebook) ➜ Est avant tout une architecture faite pour React, très similaire à Redux.
- Ngrx ➜ Basé sur Redux, mais comprend des Observable, spécialisé pour Angular
- …
Conclusion
La notion de state management nous apporte les bénéfices suivants
- Plus de visiibilité sur les modifications d’état, fini les objets modifiés par x références passées à droite à gauche.
- L’application devient donc plus prédictible.
- Les modifications peuvent être tracés ou annulées ➜ les modifications étant toutes centralisées dans des reducers il est facile de donner un comportement par défaut à chacun.
- Plus de facilité pour tester l’application ➜ Grâce à la propriété “pure” des fonctions
Voila pour la partie théorique je vous propose maintenant de suivre sur l’article suivant dont le sujet est l’implémentation avec ngrx dans le cadre d’une application Angular. Rendez-vous par là -> Librairies NGRX pour une application Angular réactive. Part 1⁄3 : ngrx/store