Le hook useReducer pour gérer les états complexes
Qu’est ce qu’un état complexe ?
Comme vous le savez,
un état (ou state) est un objet qui représente l’état interne d’un composant React. Sa création est assurée par le hook useState qui donne également vie au setter du state.
Dans la plupart des cas, un state représente une variable simple comme un nombre ou une chaine de caractères. Dans ce genre de situation, le
hook useState est suffisant pour le gérer. Cependant, il arrive parfois qu’un composant soit complexe.
On dit qu’un composant est complexe
- s’il s’agit d’un objet composé de plusieurs propriétés
- s’il s’agit d’un tableau renfermant beaucoup de données
- si ses transitions (les nouvelles valeurs qu’il peut avoir) dépendent fortement des états précédents.
- si les valeurs qu’il peut avoir dépendent de plusieurs types d’actions
Notez qu’il s’agit là des cas les plus connus d’un état complexe. Donc, il peut bien y en avoir d’autres. Mais en général, c’est le développeur qui estime si un état est complexe ou non selon le contexte d’utilisation.
Le hook useReducer
Le principe du hook
useReducer est en quelque sorte similaire à celui du hook
useState. En effet, les deux servent à gérer les states en déclenchant un rerendering de la vue après chaque modification. Cependant, useReducer est préféré pour gérer les états complexes. Par conséquent, son mode d'utilisation est un peu différent de celui de useState.
Notez que l'on peut continuer à gérer les états complexes avec le hook useState, mais l'opération sera trop complexe et la logique du composant deviendra moins claire.
Le hook useReducer s'articule sur 3 notions essentielles, à savoir le reducer, le dispatcher et l'action.
Pour mieux cerner ces notions là, voyons cet exemple qui permet d'incrémenter, décrémenter ou initialiser un compteur:
import { useReducer } from "react";
function App() {
function reducerCpt(state,action){
switch(action){
case "-": return state-1;
case "+": return state+1;
case "0": return 0;
default: return state
}
}
const [cpt,dispatcherCpt] = useReducer(reducerCpt,0)
return (
<>
<button onClick={function(){
atcherCpt("-")}}> - </button>
<button onClick={function(){
dispatcherCpt("+")}}> + </button>
<button onClick={function(){
dispatcherCpt("0")}}> Initialiser </button>
<hr />
<h1>{cpt}</h1>
</>
);
}
export default App;
Regardons un peu comment tout cela fonctionne:
Tout d'abord, regardez comment le hook
useReducer est invoqué (après avoir importé le module correspondant):
const [cpt,dispatcherCpt] = useReducer(reducerCpt,0)
Comme vous le constatez, ça ressemble un peu à la structure de déclaration du hook
useSatate, sauf que là on doit noter les points suivants:
- cpt représente le state initialisé à 0 (le deuxième argument de useReducer).
- dispatcherCpt est connu sous le nom de dispatcher. Il s'agit d'un intermédiaire qui envoie les actions au reducer.
- reducerCpt est le reducer. C'est la fonction qui contient la logique de gestion du state.
En gros, on n'appelle pas directement le reducer, mais le dispatcher en lui passant comme argument l'action souhaitée. Ensuite, c'est le dispatcher qui appelle le reducer en lui fournissant l'action.
Regardons maintenant comment on a associé le dispatcher à l'événement click dans les boutons:
<button onClick={function(){
dispatcherCpt("-")}}> - </button>
<button onClick={function(){
dispatcherCpt("+")}}> + </button>
<button onClick={function(){
dispatcherCpt("0")}}> Initialiser </button>
Vous avez constaté qu'on a appelé le dispatcher à travers une fonction anonyme, et pour cause, il y a un argument qui lui est passé. En effet, si on avait fait ceci:
<button onClick={dispatcherCpt("-")}> - </button>
le dispatcher s'exécuterait au chargement de l'application et n'attendrait pas le click. C'est un phénomène que nous avons déjà traité dans la leçon consacrée aux
callbaks en Javascript. Par contre, s'il n'y avait pas d'arguments, alors on pourrait spécifier directement le nom de la fonction à exécuter (sans parenthèses), comme on l'a fait pour le setter du state.
En tout cas, quand on appelle le dispatcher, on lui passe comme argument l'action voulue. Cette action décidera de comment le state va changer. Et c'est justement le rôle du reducer.
function reducerCpt(state,action){
switch(action){
case "-": return state-1;
case "+": return state+1;
case "0": return 0;
default: return state
}
}
Le reducer accueille deux arguments, la valeur courante du state et l'action (qu'il reçoit par l'intermédiaire du dispatcher). La valeur retournée par le reducer constitue la nouvelle valeur du state. Par conséquent, à chaque fois le state change, un rerendering est effectué.
Nous avons utilisé une simple structure switch...case pour contrôler la valeur du state. Le traitement par défaut se contente de maintenir la valeur du state inchangée.
Je rappelle que l'exemple que nous avons vu ici ne nécessite pas vraiment l'utilisation du useReducer, car le state n'est pas vraiment complexe, même s'il y a des actions différentes. Donc, on aurait pu assurer ce traitement avec un simple useState. Cependant, l'objectif est de vous montrer la méthode d'utilisation du hook useReducer si jamais vous en sentez le besoin dans votre application.
Le hook useReducer a inspiré la
bibliothèque Redux qu permet de centraliser la gestion des états dans une application React.