Concevoir une application avec Redux
Introduction
Dans les deux premiers chapitres, nous avons vu les bases de React, qui vous permettent déjà de créer vos premières applications. En effet, nous avons vu comment créer des composants, qui stockent des données (le state), et se les transmettent par le biais des propriétés. Dans l’exemple de gestion de liste de tâches présenté, les données étaient stockées dans le composant principal de l’application (App), qui les fournissait aux composants qui en avaient besoin.
Ce fonctionnement est parfaitement acceptable dans une application ; il peut néanmoins devenir laborieux à gérer et maintenir lorsque l’application stocke beaucoup de données, qui seront utilisées par beaucoup de composants. C’est pour répondre à ce problème que Redux entre en jeu.
Créé en 2015, Redux se décrit comme un conteneur d’état prévisible (predictable state container en anglais). Il permet de stocker l’état d’une application, tout en donnant les moyens d’y accéder de manière globale, et de le mettre à jour. Il n’est pas spécifique à React, c’est la bibliothèque React-Redux qui fournit les éléments permettant de faire travailler React et Redux en collaboration.
Pourquoi Redux est-il particulièrement...
Découverte de Redux
Avant de nous lancer dans un exemple mettant en œuvre Redux, parcourons rapidement quelques concepts qu’il définit afin de comprendre comment il fonctionne. Il faut savoir que ce fonctionnement est en réalité très simple, mais comme souvent les choses les plus simples permettent, mises bout à bout, d’arriver aux solutions les plus élégantes.
1. Concepts de base
a. Le state
L’élément le plus important de Redux est le state. C’est en effet sa principale fonction : stocker l’état d’une application. Les données qu’il stocke peuvent être de tout type : objets, nombres, tableaux, fonctions, etc. Le state peut être lu (par un composant React ou autre) ; en revanche, il ne sera jamais modifié directement. Nous allons devoir indiquer à Redux comment en générer la version suivante.
b. Les actions
Afin de mettre à jour le state, la première étape sera de déclencher une action, par exemple au clic sur un bouton. On dira alors que l’action est dispatchée. Une action doit être un objet, constitué d’un type, et éventuellement d’autres données.
L’idéal est de concevoir une action comme un verbe, associé à des paramètres. Par exemple, si le state contient un compteur (exemple classique d’introduction à Redux), on pourrait avoir les actions incrémenter et définirValeur(valeur), la seconde étant une action qui prend un paramètre valeur.
c. Le reducer
Le reducer est une fonction, prenant en paramètres un state et une action, et renvoyant un nouveau state. Par exemple, pour un compteur, à partir d’un state valant 5 et d’une action incrémenter, nous renverrions comme nouveau state la valeur 6. Nous ne mettons donc pas à jour le state, nous indiquons simplement à Redux une nouvelle version à prendre en compte.
Note importante : le reducer doit être une fonction pure, au sens défini par la programmation fonctionnelle, c’est-à-dire notamment :
Que son comportement et sa valeur de retour doivent être déterministes : pour un ensemble de paramètres donnés, on doit toujours avoir le même retour.
Qu’elle ne doit pas avoir...
Utilisation avec React
Dans l’exemple précédent, vous avez dû penser qu’utiliser Redux était laborieux : beaucoup d’opérations sont nécessaires pour un résultat modeste, etc. Mais c’est en le couplant avec React (ou une autre bibliothèque comme Vue.js ou Angular) que tout son potentiel est visible. Il va nous permettre d’extraire un maximum de logique métier (mise à jour du state, appels à une API, etc.) des composants, pour n’y conserver que leur responsabilité originale : le rendu.
1. Découverte de React-Redux
Utiliser Redux comme nous venons de le voir avec un composant React serait tout à fait envisageable. On pourrait par exemple créer notre store, puis le passer en propriété de chaque composant de l’application, qui serait alors en mesure d’afficher des valeurs du state, et de dispatcher des actions. Il y aurait cependant certaines subtilités à gérer, et cela resterait laborieux.
Heureusement, la bibliothèque React-Redux est là pour nous faciliter grandement la tâche. En effet, nous n’aurons pas besoin de nous soucier du store, car elle nous permet de définir pour certains composants :
-
à quels éléments du state nous souhaitons avoir accès (pour les afficher par exemple) ;
-
quelles actions nous souhaitons être mesure de dispatcher.
Reprenons par exemple notre compteur. Créons un composant similaire à ce que nous avons vu dans la section précédente, mais bien en React cette fois-ci et non en HTML classique :
// Counter.js
const Counter = ({ counter, increment }) => (
<p>
Compteur:
<span>{counter}</span>
<button onClick={() => increment()}>+</button>
</p>
)
Counter.propTypes = {
counter: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired
}
export default Counter
Rien de nouveau dans ce composant ; on attend que deux propriétés lui soient passées : la valeur du compteur counter et une fonction increment permettant d’incrémenter le compteur. Pas encore de trace de Redux, nous y venons.
Où souhaitons-nous...
Actions complexes et asynchrones
Jusque-là, notre manière d’utiliser Redux est restée relativement simple :
-
dans le composant nous dispatchons une action ;
-
l’action passe dans le reducer, qui met à jour le state ;
-
le composant est réaffiché pour tenir compte du nouveau state.
Si ce processus convient déjà à beaucoup de besoins, certains vont nous poser quelques problèmes : comment peut-on indiquer qu’une action doit à son tour dispatcher une autre action ? Comment peut-on faire en sorte qu’une action déclenche une requête HTTP (asynchrone donc) ?
Pour répondre à ces problèmes, il existe une bibliothèque qui va venir se greffer à Redux : Redux-Thunk (https://github.com/reduxjs/redux-thunk). Celle-ci se présente sous forme d’un middleware pour Redux, c’est-à-dire que l’on va indiquer à Redux que l’on souhaite l’utiliser, et cela suffira à ajouter de nouvelles possibilités que nous allons voir.
Afin de présenter Redux-Thunk, nous allons créer un chronomètre minimaliste, qui offrira les fonctionnalités suivantes :
-
être démarré, déclenchant ainsi l’affichage du nombre de secondes écoulées à partir de ce moment ;
-
être mis en pause momentanément ;
-
être remis à zéro.
Cet exemple ne pose aucune difficulté technique, d’ailleurs grâce au premier chapitre de ce livre, en utilisant un state local pour le composant, vous n’auriez sans doute aucun problème pour le réaliser. Mais ici nous souhaitons utiliser Redux !
Commençons par initialiser un projet semblable à ceux que nous avons créés jusque-là. Pour cela, vous pouvez récupérer les fichiers package.json et public/index.html d’un exemple précédent, comme celui associé à la section sur React-Redux.
Nous allons tout d’abord créer notre store grâce à ce que nous savons déjà faire. Le state sera composé d’un attribut time contenant le temps écoulé en millisecondes, ainsi qu’un booléen isRunning, vrai si le chronomètre a été démarré et n’a pas été...
Un exemple complet
À présent que nous avons vu les principales fonctionnalités de Redux, voyons comment mettre tout cela en pratique avec un exemple plus complet. Dans cette section nous allons réaliser une application minimaliste permettant d’afficher la liste des sujets postés sur le subreddit consacré à React. Lorsque l’utilisateur cliquera sur le titre d’un sujet, nous afficherons alors le texte associé à ce sujet.
Concernant l’appel à l’API de Reddit, c’est relativement simple puisque la plupart du temps il suffit, à partir d’une page Reddit (par exemple https ://www.reddit.com/r/reactjs), d’ajouter le suffixe « .json » pour obtenir les mêmes données que la page, mais au format JSON : https ://www.reddit.com/r/reactjs.json. Cette URL est la première que nous appellerons pour obtenir la liste des sujets postés. Pour obtenir les détails sur un sujet (et notamment le texte associé), l’attribut permalink dans le JSON nous permettra de construire l’URL à appeler pour obtenir le JSON.
Comme cet exemple est plus complexe que ce que nous avons vu précédemment, nous allons voir comment scinder notre store en différents modules, que nous appellerons services. Cette terminologie n’a rien d’un standard, et il se peut que vous entendiez un autre terme à la place. Pour ma part, j’entends par service un module exportant des actions et un reducer.
Cet exemple comportant plus de code qu’habituellement, il ne sera peut-être pas facile de le réaliser par vous-même à la simple lecture de ce livre. Aussi vous pouvez vous rendre directement sur le dépôt GitHub des exemples du livre afin de le récupérer et le faire tourner.
1. Les services
La partie store de notre application sera constituée de trois services :
-
posts s’occupera de la récupération des sujets du subreddit React.
-
postsDetails ira chercher les détails pour les sujets (lorsqu’ils seront cliqués).
-
routing se chargera du cycle de vie de l’application, c’est-à-dire d’afficher un sujet spécifique dès qu’il est cliqué, et de réafficher la liste lorsque le bouton retour est cliqué.
Chacun de ces services...
Conclusion
Voilà qui est fait pour notre exploration de Redux. J’espère que vous aurez apprécié les avantages qu’il procure par rapport à l’utilisation d’un state local à un composant, notamment en termes de maintenabilité et d’architecture. En guise de conclusion, laissez-moi évoquer l’une des questions qui pose encore du souci à de nombreux développeurs React et divise parfois la communauté : Redux ou state local ?
Si vous avez déjà essayé de créer une grosse application, que ce soit avec React ou non, stocker les données de l’application de manière à ce qu’elles soient accessibles de partout, tout en gérant efficacement leur mise à jour, vous est sans doute déjà apparu comme un problème conséquent. Redux apporte une solution à ce problème, avec ses avantages et ses inconvénients. Pour l’avoir utilisé intensément, je lui attribue les avantages suivants :
-
Les données sont facilement accessibles depuis n’importe quel composant, sans avoir à passer des dizaines de propriétés aux composants intermédiaires dans la hiérarchie.
-
La mise à jour des données dans un mécanisme ultrasimple car totalement déterministe et sans effet de bord (les reducer) permet...