Dans cet article, nous allons nous intéresser à la libraire Redux développé par Dan Abramov, membre de la core team React.
Redux est une des implémentations les plus populaires du pattern Flux, introduit par Facebook et permettant une gestion centralisée d’une application web.
Pour résumer rapidement, un évènement utilisateur (ex : clic sur un bouton) va déclencher une action, qui va mettre à jour l’état global de l’application et en retour, mettre à jour l’interface de l’application.
Mais, pour mieux comprendre le fonctionnement de Redux, nous allons le coder pas à pas pour finalement l’intégrer dans une application React.
Un state global
Le B.A.BA de la librairie Redux, c’est la gestion de l’état global (le state) de notre application. Pour respecter le vocabulaire Redux, nous allons créer un objet store chargé de gérer tout ça.
Cet objet store
va donc posséder une variable interne state
.
Celui-ci est un simple objet javascript ne pouvant pas être modifier depuis l’extérieur du store.
Par contre, si on ne peut pas le modifier, il faut tout de même pouvoir lire sa valeur. C’est pourquoi on crée une fonction getState
qui retourne la valeur du state. Ce dernier n’est en l’état absolument pas générique et réutilisable (nous l’améliorerons par la suite).
Pour l’instant, il contient une clé counter
initialisée à 0.
|
|
Dispatcher une action et modifier le state
En l’état, on ne peut pas modifier la variable counter du state
. On va donc, en suivant le vocabulaire de Flux et Redux, permettre de dispatcher un event, c’est à dire informer le store qu’une action pouvant éventuellement modifier le state
a eu lieu.
En l’occurence, pour notre exemple, on va incrémenter ou décrémenter counter
suivant l’action que l’on envoie. Ici, on va prendre en compte deux actions, ‘ADD’ et ‘SUBSTRACT’ qui vont modifier le state
en conséquence.
Cette action va donc être prise en compte par une nouvelle fonction dispatch
qui prend en entrée le nom de l’action.
|
|
Maintenant, en plus de la lecture du state
via getState
, on peut donc le modifier indirectement avec la méthode dispatch
.
Rendre notre store plus générique
Dans l’exemple précédent, le state
initial est hardcodé, ce qui n’est pas génial si l’on souhaite faire autre chose qu’un compteur…, ce qui a de forte chance d’arriver :)
On va donc modifier Store
de tel sorte qu’il accepte en entrée un argument initialState
(qui sera un objet vide par défaut), qui sera utilisé - comme son nom l’indique - pour assigner une état initial à notre state
.
|
|
De même, la fonction dispatch
définit précédemment contient toute la logique de mise à jour en interne, il faut donc l’externaliser.
Pour réaliser cela, on amène un autre élément essentiel de Redux que l’on avait survolé: le reducer. Ce dernier est une fonction qui prend en argument le state
actuel, à l’instant T et l’action qui vient d’être dispatchée pour retourner le state
suivant, le nouveau state
de notre application.
Le reducer sera le deuxième argument que va recevoir notre objet store à l’instanciation et va venir instancier la variable reducer
de notre Store lors de l’instanciation.
|
|
Le reducer est maintenate un fichier à part:
|
|
Pour instancier le store, on écrit maintenant :
|
|
Maintenant, on a quelque chose qui commence à ressembler pas mal au code que l’on a quand on utilise Redux. On crée un store en définissant un state initial et le reducer associé qui viendra le modifier.
Maintenant, fini de jouer dans la console javascript: on va tenter de lier ça à React.
React et notre Redux homemade
Nous allons développer une application React très simple mais, pour rendre ça plus sympa à regarder, on va utiliser react-semantic-ui, une librairie qui regroupe plein de composants prêts à l’emploi.
Pour tester notre code avec React, nous allons créer une boîte mail qui affiche de faux mails. Au niveau de l’UI, deux actions seront permises par l’utilisateur via un bouton refresh qui va incrémenter le nombre de faux mails et un bouton supprimer qui va décrémenter le nombre de mails.
Cela nous permet d’utiliser quasiment tel quel le store
que l’on vient d’écrire. On l’étoffera par la suite.
React et notre Redux: Etape #1
Première étape, la création d’un composant React qui va accéder au contenu du state
et l’afficher.
Dans la méthode render
, on va donc appeler la méthode getState
du store
pour accéder et afficher la valeur de counter
.
|
|
Maintenant, on va créer la méthode permettant d’incrémenter la valeur counter
du state
lorsque l’on clique sur le bouton refresh.
On va donc rajouter une méthode onClick
au refresh button via l’ajout de la ligne onClick={this.hitRefreshButton}
et écrire ce que va réaliser la méthode hitRefreshButton
. En l’occurence, il s’agit ici de dispatcher une action ADD au store :
|
|
Maintenant, essayons de rafraîchir le nombre de mails en cliquant.
Alors…ah, rien ne se passe… Quand, on console.log
le state, celui-ci est bien mis à jour, pourtant, le nombre de mails affichés reste désespérement à 0.
Que se passe t-il ?
On a un composant React qui affiche le state
contenu dans le store
à un instant T et deux boutons qui dispatchent des actions pour mettre à jour le state. Une fois la mise à jour terminée, le composant React est-il au courant qu’il doit effectuer un nouveau rendering ?
NON car personne ne le prévient…
Un composant React va effectuer un nouveau rendering soit :
1) avec la mise à jour des props envoyés par son composant parent
2) en invoquant sa méthode interne setState
3) en invoquant sa méthode interne forceUpdate
qui comme son nom l’indique va forcer un re-rendering.
Ici, la première solution n’est pas applicable puisqu’il n’y pas de composant parent.
La seconde ne l’est pas non plus puisque le composant n’a pas de state
interne. On pourrait éventuellement créer un state
interne pour que setState
soit appelé à chaque mise à jour du state
global mais, cela amènerait beaucoup de code dupliqué dans chacun des composants utilisant le state
dans leur affichage…
La troisième solution semble la plus simple : forcer un re-rendering du composant pour que l’UI affiche la valeur courant du state
global. Il faudrait donc qu’à l’issue d’une mise à jour du state
, le composant React soit informé qu’il doit effectuer une rendering: un callback.
Pour réaliser cela, nous allons ajouter une méthode subscribe
au Store. Cette méthode prendra en argument une fonction que l’on stockera dans un tableau listeners
(contenant uniquement des fonctions).
|
|
Maintenant, on veut que les fonctions contenues dans listeners soient appelées à chaque mise à jour du state
. Ces dernières ont lieu dans la méthode dispatch
, c’est donc à cet endroit que l’on va agir.
|
|
Maintenant que le Store est capable de rappeler des callbacks à chaque mises-à-jour, il faut légèrement modifier le composant React pour qu’il fassent partie des listeners du state
et se rerender, à chaque mise à jour de ce dernier. On va donc subscriber le composant React pour que sa méthode forceUpdate
soit appelée à chaque mise à jour.
|
|
Avec ceci, notre composant se met à jour quand le state
change !
React et notre Redux: Etape #2
Un Mail
Pour le fun, j’ai amélioré le prototype afin d’avoir une fausse boîte mail permettant au clic sur le refresh, de générer de nouveaux mails qui peuvent être marqués comme lus ou bien supprimés.
Le store ne contient donc plus uniquement une valeur counter
mais des faux mails générés (expéditeur, objet, contenu, etc.) grâce à Faker, une petit librairie très pratique pour mocker du contenu.
De plus, on en profite pour se rapprocher du formalisme de Redux en amenant ici l’écritre de noms d’actions et des fonctions associées :
|
|
De cette façon, au sein d’un composant React, on génère un mail de la façon suivante :store.dispatch(addMail());
On obtient donc ça:
Le code complet est disponible sur Github : https://github.com/GaelS/fake-inbox-redux
Conclusion
En peu de lignes de code, nous avons finalement crée un petit manager de state global que nous avons intégré dans une application React. L’exercice est très sympa pour démystifier Redux, cette librairie incontournable de l’écosystème React.
Maintenant, les limites de l’exercice sont que l’on se retrouve à devoir ajouter une méthode componentDidMount
pour forcer l’update de tous les composants dont le rendu est lié au state. De même, on doit logiquement importer le store
lorsque l’on souhaite accéder au méthode dispatch
, subscribe
ou getState
.
On verra dans un prochain article comment améliorer cette connexion entre React et Redux…
Teaser: je pense que la librairie react-redux sera notre amie pour l’inspiration :)
Merci de m’avoir lu et n’hésitez pas à laisser un commentaire juste en dessous.