Extraire la logique d'état dans un réducteur
Les composants avec de nombreuses mises à jour d'état réparties dans plusieurs gestionnaires d'événements peuvent devenir accablants. Dans ces cas, vous pouvez consolider toute la logique de mise à jour de l'état en dehors de votre composant dans une seule fonction, appelée unréducteur.
Vous apprendrez
- Ce qu'est une fonction réductrice
- Comment refactoriser
useStateenuseReducer - Quand utiliser un réducteur
- Comment en écrire un correctement
Consolider la logique d'état avec un réducteur
À mesure que vos composants gagnent en complexité, il peut devenir plus difficile de voir d'un coup d'œil toutes les différentes façons dont l'état d'un composant est mis à jour. Par exemple, le composantTaskAppci-dessous contient un tableau detâchesdans l'état et utilise trois gestionnaires d'événements différents pour ajouter, supprimer et modifier des tâches :
Chacun de ses gestionnaires d'événements appellesetTasksafin de mettre à jour l'état. À mesure que ce composant grandit, la quantité de logique d'état dispersée augmente également. Pour réduire cette complexité et garder toute votre logique dans un endroit facile d'accès, vous pouvez déplacer cette logique d'état dans une seule fonction en dehors de votre composant,appelée un « réducteur ».
Les réducteurs sont une façon différente de gérer l'état. Vous pouvez migrer deuseStateversuseReduceren trois étapes :
- Passerde la définition de l'état au dispatch d'actions.
- Écrireune fonction réductrice.
- Utiliserle réducteur depuis votre composant.
Étape 1 : Passer de la définition de l'état au dispatch d'actions
Vos gestionnaires d'événements spécifient actuellementce qu'il faut faireen définissant l'état :
Supprimez toute la logique de définition de l'état. Il vous reste trois gestionnaires d'événements :
handleAddTask(text)est appelé lorsque l'utilisateur appuie sur « Ajouter ».handleChangeTask(task)est appelé lorsque l'utilisateur bascule une tâche ou appuie sur « Enregistrer ».handleDeleteTask(taskId)est appelé lorsque l'utilisateur appuie sur « Supprimer ».
La gestion de l'état avec des réducteurs est légèrement différente de la définition directe de l'état. Au lieu de dire à React « ce qu'il faut faire » en définissant l'état, vous spécifiez « ce que l'utilisateur vient de faire » en dispatchant des « actions » depuis vos gestionnaires d'événements. (La logique de mise à jour de l'état résidera ailleurs !) Ainsi, au lieu de « définirtasks» via un gestionnaire d'événements, vous dispatchez une action « ajouté/modifié/supprimé une tâche ». Cela décrit mieux l'intention de l'utilisateur.
L'objet que vous passez àdispatchest appelé une « action » :
C'est un objet JavaScript ordinaire. Vous décidez de ce que vous y mettez, mais généralement il doit contenir les informations minimales surce qui s'est passé. (Vous ajouterez la fonctiondispatchelle-même dans une étape ultérieure.)
Remarque
Un objet d'action peut avoir n'importe quelle forme.
Par convention, il est courant de lui donner une chaînetypequi décrit ce qui s'est passé, et de transmettre toute information supplémentaire dans d'autres champs. Letypeest spécifique à un composant, donc dans cet exemple,'added'ou'added_task'conviendraient. Choisissez un nom qui indique ce qui s'est passé !
Étape 2 : Écrire une fonction réductrice
Une fonction réductrice est l'endroit où vous placerez votre logique d'état. Elle prend deux arguments, l'état actuel et l'objet d'action, et elle renvoie le prochain état :
React définira l'état avec ce que vous retournez depuis le réducteur.
Pour déplacer votre logique de définition d'état de vos gestionnaires d'événements vers une fonction réductrice dans cet exemple, vous allez :
- Déclarer l'état actuel (
tasks) comme premier argument. - Déclarer l'objet
actioncomme second argument. - Retourner leprochainétat depuis le réducteur (que React utilisera pour définir l'état).
Voici toute la logique de définition d'état migrée vers une fonction réductrice :
Comme la fonction réductrice prend l'état (tasks) comme argument, vous pouvezla déclarer en dehors de votre composant.Cela réduit le niveau d'indentation et peut rendre votre code plus facile à lire.
Remarque
Le code ci-dessus utilise des instructions if/else, mais il est courant d'utiliser desinstructions switchdans les réducteurs. Le résultat est le même, mais les instructions switch peuvent être plus faciles à lire d'un coup d'œil.
Nous les utiliserons tout au long du reste de cette documentation, comme ceci :
Nous recommandons d'encadrer chaque bloccaseavec des accolades{et}afin que les variables déclarées dans différentscasen'entrent pas en conflit. De plus, uncasedoit généralement se terminer par unreturn. Si vous oubliez lereturn, le code « passera » aucasesuivant, ce qui peut entraîner des erreurs !
Si vous n'êtes pas encore à l'aise avec les instructions switch, utiliser if/else est tout à fait acceptable.
Étape 3 : Utiliser le réducteur depuis votre composant
Enfin, vous devez connecter letasksReducerà votre composant. Importez le HookuseReducerdepuis React :
Ensuite, vous pouvez remplaceruseState:
paruseReducercomme ceci :
Le HookuseReducerest similaire àuseState—vous devez lui passer un état initial et il retourne une valeur d'état et un moyen de définir l'état (dans ce cas, la fonction de dispatch). Mais il est un peu différent.
Le HookuseReducerprend deux arguments :
- Une fonction réductrice
- Un état initial
Et il retourne :
- Une valeur d'état
- Une fonction de dispatch (pour « dispatcher » les actions de l'utilisateur vers le réducteur)
Maintenant, tout est connecté ! Ici, le réducteur est déclaré en bas du fichier du composant :
Si vous le souhaitez, vous pouvez même déplacer le réducteur dans un fichier différent :
La logique du composant peut être plus facile à lire lorsque vous séparez les préoccupations de cette manière. Maintenant, les gestionnaires d'événements spécifient seulementce qui s'est passéen expédiant des actions, et la fonction réductrice déterminecomment l'état est mis à jouren réponse à celles-ci.
Comparaison entreuseStateetuseReducer
Les réducteurs ne sont pas sans inconvénients ! Voici quelques façons de les comparer :
- Taille du code :Généralement, avec
useState, vous devez écrire moins de code initialement. AvecuseReducer, vous devez écrire à la fois une fonction réductriceetexpédier des actions. Cependant,useReducerpeut aider à réduire le code si de nombreux gestionnaires d'événements modifient l'état de manière similaire. - Lisibilité :
useStateest très facile à lire lorsque les mises à jour de l'état sont simples. Lorsqu'elles deviennent plus complexes, elles peuvent alourdir le code de votre composant et le rendre difficile à parcourir. Dans ce cas,useReducervous permet de séparer proprement lecommentde la logique de mise à jour duce qui s'est passédes gestionnaires d'événements. - Débogage :Lorsque vous avez un bug avec
useState, il peut être difficile de direoùl'état a été défini incorrectement, etpourquoi. AvecuseReducer, vous pouvez ajouter un log console dans votre réducteur pour voir chaque mise à jour de l'état, etpourquoicela s'est produit (en raison de quelleaction). Si chaqueactionest correcte, vous saurez que l'erreur se trouve dans la logique du réducteur elle-même. Cependant, vous devez parcourir plus de code qu'avecuseState. - Tests :Un réducteur est une fonction pure qui ne dépend pas de votre composant. Cela signifie que vous pouvez l'exporter et la tester séparément, de manière isolée. Bien qu'il soit généralement préférable de tester les composants dans un environnement plus réaliste, pour une logique de mise à jour d'état complexe, il peut être utile de vérifier que votre réducteur renvoie un état particulier pour un état initial et une action donnés.
- Préférence personnelle :Certaines personnes aiment les réducteurs, d'autres non. C'est normal. C'est une question de préférence. Vous pouvez toujours convertir entre
useStateetuseReducerdans les deux sens : ils sont équivalents !
Nous recommandons d'utiliser un réducteur si vous rencontrez souvent des bugs dus à des mises à jour incorrectes de l'état dans un composant, et que vous souhaitez introduire plus de structure dans son code. Vous n'êtes pas obligé d'utiliser des réducteurs pour tout : n'hésitez pas à les mélanger ! Vous pouvez même utiliseruseStateetuseReducerdans le même composant.
Écrire de bons réducteurs
Gardez ces deux conseils à l'esprit lorsque vous écrivez des réducteurs :
- Les réducteurs doivent être purs.Comme lesfonctions de mise à jour d'état, les réducteurs s'exécutent pendant le rendu ! (Les actions sont mises en file d'attente jusqu'au prochain rendu.) Cela signifie que les réducteursdoivent être purs—les mêmes entrées produisent toujours la même sortie. Ils ne doivent pas envoyer de requêtes, planifier des délais d'attente ou effectuer des effets de bord (opérations qui impactent des éléments extérieurs au composant). Ils doivent mettre à jour lesobjetset lestableauxsans mutation.
- Chaque action décrit une seule interaction utilisateur, même si cela entraîne plusieurs changements dans les données.Par exemple, si un utilisateur appuie sur "Réinitialiser" dans un formulaire avec cinq champs gérés par un réducteur, il est plus logique de dispatcher une action
reset_formplutôt que cinq actionsset_fielddistinctes. Si vous enregistrez chaque action dans un réducteur, ce journal doit être suffisamment clair pour que vous puissiez reconstruire quelles interactions ou réponses se sont produites et dans quel ordre. Cela aide au débogage !
Écrire des réducteurs concis avec Immer
Tout comme pour lamise à jour d'objetset destableauxdans un état régulier, vous pouvez utiliser la bibliothèque Immer pour rendre les réducteurs plus concis. Ici,useImmerReducervous permet de muter l'état avec des assignations commepushouarr[i] = :
Les réducteurs doivent être purs, donc ils ne doivent pas muter l'état. Mais Immer vous fournit un objet spécialdraftqu'il est sûr de muter. En coulisses, Immer créera une copie de votre état avec les modifications que vous avez apportées audraft. C'est pourquoi les réducteurs gérés paruseImmerReducerpeuvent muter leur premier argument et n'ont pas besoin de retourner l'état.
Récapitulatif
- Pour convertir de
useStateàuseReducer:- Dispatchez des actions depuis les gestionnaires d'événements.
- Écrivez une fonction réductrice qui retourne le prochain état pour un état et une action donnés.
- Remplacez
useStateparuseReducer.
- Les réducteurs nécessitent d'écrire un peu plus de code, mais ils aident au débogage et aux tests.
- Les réducteurs doivent être purs.
- Chaque action décrit une seule interaction utilisateur.
- Utilisez Immer si vous voulez écrire des réducteurs dans un style mutatif.
Try out some challenges
Challenge 1 of 4:Dispatch actions from event handlers #
Currently, the event handlers in ContactList.js and Chat.js have // TODO comments. This is why typing into the input doesn’t work, and clicking on the buttons doesn’t change the selected recipient.
Replace these two // TODOs with the code to dispatch the corresponding actions. To see the expected shape and the type of the actions, check the reducer in messengerReducer.js. The reducer is already written so you won’t need to change it. You only need to dispatch the actions in ContactList.js and Chat.js.
