Introduction
Dans un précédent article, nous avons vu comment implémenter de façon naïve la librairie Redux. Si vous ne l’avez pas encore lu ou que vous souhaitez simplement le relire, c’est par ici: Redux from scratch
À l’issue du tuto précédent, on avait donc un store qui mettait à jour nos composants React. On pouvait être satisfait! Seulement, on pouvait réaliser les limites de notre implémentation… En effet, chaque composant dépendant de notre store doit nécessairement l’importer et y subscriber dans la méthode componentDidMount
de la façon suivante: store.subscribe(() => this.forceUpdate)
.
Certes, ça ne représente que deux lignes de code dans chaque fichier mais cela nous amène à écrire un peu partout du code identique en plus de lier très fortement nos composants à l’implémentation de notre store. Ce qui est problématique si à terme, on souhaite publier nos composants sur npm par exemple.
La solution existe déjà et s’appelle React Redux et nous allons l’implémenter ensemble !
Store everywhere…
Chacun des composants ayant besoin du state et des méthodes à dispatcher doit d’avoir accès d’une façon ou d’une autre au store. Il faut donc que ce dernier soit disponible PARTOUT!
Deux options s’offrent à nous :
1) Transmettre le store depuis le composant App
, le composant le plus haut dans l’arborescence de l’application, en props à tous les composants enfants.
2) Utiliser le contexte
L’option numéro 1, on le réalise vite, va se révéler fastidieuse quand le composant tout en bas, bien loin en bas, de notre arborescence, aura besoin d’accéder au store. On va se retrouver un ensemble de composant qui se passeront gentiment le store
jusqu’à celui qui en aura forcément besoin…
Bon j’avoue la comparaison est un peu biaisé, mais l’option 2 utilise la notion de contexte hors, le contexte dans React vient justement pour éviter les biais de l’option 1. Le contexte (un simple objet javascript) se définit au sein d’un composant et est transmis par défaut à tous les composants enfants. Cela veut dire que si nécessaire, les composants enfant pourront accéder à cet objet pour l’utiliser. Ainsi, même un le composant au bout du bout de l’arborescence pourra utiliser le contexte définit 10 niveaux de hiérarchie au dessus de lui de façon très simple.
C’est donc évidemment cette option que nous allons utiliser pour rendre notre store disponible partout dans notre application.
On va donc tenter d’écrire un composant Provider
qui viendra wrapper le composant principal App
qui permettra de rendre accessible le store partout dans notre application et nous fera réécrire le fichier index.js de cette façon :
|
|
Ce Provider
est donc un élément React qui prend un seul props : le store
si vous avez suivi et va donc le rendre disponible via le contexte aux composant enfant.
Comment fait-on cela me direz-vous ? On va y aller par étape.
Premièrement, ce composant ne gère aucun affichage en tant que tel, il se contente de transmettre de l’information aux composants enfants mais ne va en aucun cas modifier l’UI :
|
|
On a maintenant un composant simple qui se contente de rendre son props children
. Là en l’occurence, avec ou sans Provider
, rien n’a changé pour notre application si ce n’est un niveau de hiérarchie supplémentaire dans notre arbre de composants.
On va maintenant rendre Provider
vraiment utile. Pour créer un contexte, il faut utiliser la méthode getChildContext
, méthode qui retourne un objet qui sera le contexte auquel les composants enfants auront accès. Ici, cete méthode va retourner le store
qui est passé en props
à notre composant Provider
.
|
|
Pour que ce soit totalement complet et fonctionnel, il faut rajouter, en plus de cette méthode, le type des attributs que le contexte contient (la docs est ici ).
Ainsi, Provider
va finalement s’écrire de la façon suivante:
|
|
Done, notre composant Provider
est maintenant capable de passer le store
en context. le store
est accessible à tous les composants enfant, VICTOIRE !!
Euh…attendez, pour le moment, on a rien simplifié puisque en l’état, il faut que l’on récupère le contexte dans tous les composants qui utilisent le store
et ensuite, nous devons toujours subscriber
aux mises à jour et dispatcher
les actions. Finalement, on s’évite un import de fichier mais nos composant restent toujours très lié à l’implémentation de notre store
, donc en l’état, on ne s’est pas encore simplifié la vie…. Comment changer ça ?
Single store looking for soul mate(s), answer please…
Ce que l’on souhaite, c’est que nos composants React soient totalement découplés du store
, qu’ils n’aient pas besoin de subscriber
, de connaître les actions du store
ou d’appeler directement getState
. On veut des composants simples, qui reçoivent des props
et gèrent un rendu en conséquence, point à la ligne.
C’est ce que nous permet la fonction connect
de React-Redux, et c’est cette méthode que nous allons réimplémenter. Cette fonction va nous permettre de retourner d’office un composant React couplé au store
. Elle s’utilise de a façon suivante:
|
|
Ouch, entre la fonction qui retourne une fonction, des noms d’arguments à rallonge mais WHAT?, c’est l’anarchie ce truc ?! Pas de panique, on va y aller en douceur :)
Ce qu’il faut avoir à l’esprit avant toute chose, c’est que l’idée de connect
, c’est d’encapsuler un composant (en l’occurence dans notre exemple précédent, le bien-nommé ComponsantQueLonSouhaiteLierAuStore
afin de lui fournir tous les props
dont il a besoin sans pour s’afficher sans se soucier du store
.
Il se contenet de récupérer les élements et de les afficher, fin.
Les 2 arguments de connect
sont des fonctions et possède des noms assez explicites pour les anglophones. La fonction mapDispatchToProps
(resp. mapStateToProps
) prend pour argument dispatch
(resp. state
) et a pour objectif de retourner un objet constitué d’éléments dérivés de la fonction dispatch
(resp. de l’objet state
).
Pour le moment, on va s’arrêter à ça, nous verrons leur utilisation dans un cas pratique. Pour le moment, ce qui est essentiel à savoir c’est que ces deux fonctions dépendent de dispatch
et state
et retourne des objets.
Pour le moment, on comprend que cette méthode prend deux arguments et retourne une fonction qui prend en argument un composant React:
|
|
Jusqu’ici, ça va. Maintenant, qu’est-ce que l’on voulait déjà ? Ah oui, retourner le composant React en argument avec les props
reçus ! Pour cela, on va enrichir un peu le code précédent :
|
|
Maintenant, on a bien une fonction qui retourne une fonction retournant elle-même un composant React. Sweet !
Seulement, on n’utilise même pas mapDispatchToProps
et mapStateToProps
. On se contente de retourner Component
sans le modifier. On va donc enrichir tout ça.
Pour rappel mapDispatchToProps
et mapStateToProps
prennent respectivement les objets dispatch
et state
contenu dans notre objet store
. Il faut donc accéder au store
. Comment faire ? Et oui grace au contexte de l’objet Provider
!
On va donc créer un composant React au sein de la fonction dont la charge sera de recupérer le contexte et de retourner dans sa méthode render
, l’argument Component
, l’argument de notre fonction qui aura été un peu enrichi pour l’occasion.
|
|
Maintenant, on a accès à notre store
via le context. De façon identique à this.props
ou this.state
, on accède au context via this.context
au sein d’un composant à état ou simplement context
dans un composant fonctionnel.
On peut donc appeler mapDispatchToProps
et mapStateToProps
avec les arguments attendus.
|
|
Maintenant, on veux que le composant React en argument de la fonction ait en plus de ses props
, les objets générés par mapDispatchToProps
et mapStateToProps
. On va donc enrichir la méthode render
de ConnectedComponent
.
|
|
Génial, maintenant, on retourne bien un composant qui possède des informations venant du store
. Par contre, on n’oublie quelque chose, en l’état, le composant ne va pas mettre un jour son rendu lorsque le state
est modifié, il faut penser à subscriber pour écouter les changements.
On obtient donc ceci :
|
|
Maintenant, lorsque l’on fait :
|
|
Ici, connectFunction
est une fonction qui attend en argument un composant React pour le lier directement au store
de façon très simple.
Ainsi :
|
|
Place à la pratique
Pour revenir à notre exemple de boîte mail, on va pouvoir modifier de façon assez drastique le ficher principal qui ressemblait au code juste en dessous à l’isue du tutoria sur redux
:
|
|
On voit ici qu’on appelle subscribe
, dispatch
ou encore getState
directement au sein de notre composant. Si pour une raison X ou Y, l’implémentation de store changeait, getState
devenant getCurrentState
pour des raisons obscures de lisibilité ;) il faudrait changer getState
dans tous les composants qui l’utilisent…pas top.
MAIS, avec connect
, on peut simplifier tout ça !
|
|
Et voilà, grâce à connect
, notre composant App
ne s’occupe que de ses props
, plus aucun accès à l’objet store
, ni à ses méthodes n’est nécessaire !!
###Conclusion
Avec cette découverte de react-redux
, on a complété notre apprentissage de redux
. Il aurait en effet été dommage de s’arrêter en si bon chemin alors que react-redux
est juste une librairie indispensable dès que l’on souhaite utiliser redux
dans un projet React.
Maintenant, on comprend comment et pourquoi elle existe et nous simplifie la vie puisqu’il y a bel et bien un avant et un après react-redux
.
Il suffit de le voir avec un exemple aussi simple que celui que nous avons construit au cours de l’article traitant de redux
…
Par contre, sachez que notre version de react-redux
est évidemment beaucoup, beaucoup, plus simple que le package officiel. J’ai délibérément négligé certains aspects de l’API offert par react-redux
, pas à cause de la complexité mais parce qu’il ne rajouterait pas beaucoup de plus value en terme de compréhension.
Dans une toute autre mesure, react-redux
soulève et résoud de nombreux problèmes très poussés de cohérence de la donnée causé notamment par l’asynchronisme de la méthode setState
, de caching de component, pour répondre à des edge-cases rendant le code source pas très parlant à la première lecture… :) Pour ce qui désire approfondir le sujet, Dan Abramov parle de react-redux
ici (la vidéo n’est pas de très bonne qualité malheureuseument mais le contenu oui)…
Le code détaillé dans ce poste disponible ici.
A bientôt,
Gaël