Choisir la structure de l'état
Bien structurer l'état peut faire la différence entre un composant agréable à modifier et déboguer, et un composant qui est une source constante de bugs. Voici quelques conseils à prendre en compte lors de la structuration de l'état.
Vous allez apprendre
- Quand utiliser une seule variable d'état ou plusieurs
- Ce qu'il faut éviter lors de l'organisation de l'état
- Comment résoudre les problèmes courants liés à la structure de l'état
Principes pour structurer l'état
Lorsque vous écrivez un composant qui contient un état, vous devrez faire des choix sur le nombre de variables d'état à utiliser et sur la forme de leurs données. Bien qu'il soit possible d'écrire des programmes corrects même avec une structure d'état sous-optimale, quelques principes peuvent vous guider pour faire de meilleurs choix :
- Groupez les états liés.Si vous mettez toujours à jour deux variables d'état ou plus en même temps, envisagez de les fusionner en une seule variable d'état.
- Évitez les contradictions dans l'état.Lorsque l'état est structuré de manière à ce que plusieurs parties puissent se contredire et être en « désaccord », vous laissez place aux erreurs. Essayez d'éviter cela.
- Évitez les états redondants.Si vous pouvez calculer certaines informations à partir des props du composant ou de ses variables d'état existantes pendant le rendu, vous ne devez pas mettre ces informations dans l'état de ce composant.
- Évitez la duplication dans l'état.Lorsque les mêmes données sont dupliquées entre plusieurs variables d'état ou au sein d'objets imbriqués, il est difficile de les maintenir synchronisées. Réduisez la duplication lorsque c'est possible.
- Évitez les états profondément imbriqués.Un état hiérarchique profond n'est pas très pratique à mettre à jour. Lorsque c'est possible, préférez structurer l'état de manière plate.
L'objectif derrière ces principes est derendre l'état facile à mettre à jour sans introduire d'erreurs. Supprimer les données redondantes et dupliquées de l'état aide à garantir que toutes ses parties restent synchronisées. C'est similaire à la façon dont un ingénieur de base de données pourrait vouloir« normaliser » la structure de la base de donnéespour réduire le risque de bugs. Pour paraphraser Albert Einstein,« Rendez votre état aussi simple que possible, mais pas plus simple. »
Voyons maintenant comment ces principes s'appliquent en pratique.
Grouper les états liés
Vous pouvez parfois hésiter entre l'utilisation d'une seule ou de plusieurs variables d'état.
Devriez-vous faire ceci ?
Ou ceci ?
Techniquement, vous pouvez utiliser l'une ou l'autre de ces approches. Maissi deux variables d'état changent toujours ensemble, il peut être judicieux de les unifier en une seule variable d'état.Ainsi, vous n'oublierez pas de toujours les garder synchronisées, comme dans cet exemple où le déplacement du curseur met à jour les deux coordonnées du point rouge :
Un autre cas où vous regrouperez des données dans un objet ou un tableau est lorsque vous ne savez pas combien de parties d'état vous aurez besoin. Par exemple, c'est utile lorsque vous avez un formulaire où l'utilisateur peut ajouter des champs personnalisés.
Piège
Si votre variable d'état est un objet, rappelez-vous quevous ne pouvez pas mettre à jour un seul champsans copier explicitement les autres champs. Par exemple, vous ne pouvez pas fairesetPosition({ x: 100 })dans l'exemple ci-dessus car cela n'aurait pas du tout la propriétéy! Au lieu de cela, si vous vouliez définir uniquementx, vous feriez soitsetPosition({ ...position, x: 100 }), soit vous les sépareriez en deux variables d'état et feriezsetX(100).
Éviter les contradictions dans l'état
Voici un formulaire de retour d'hôtel avec les variables d'étatisSendingetisSent :
Bien que ce code fonctionne, il laisse la porte ouverte à des états « impossibles ». Par exemple, si vous oubliez d'appelersetIsSentetsetIsSendingensemble, vous pourriez vous retrouver dans une situation oùisSendingetisSentsont tous deuxtrueen même temps. Plus votre composant est complexe, plus il est difficile de comprendre ce qui s'est passé.
PuisqueisSendingetisSentne devraient jamais êtretrueen même temps, il est préférable de les remplacer par une seule variable d'étatstatusqui peut prendre l'une destroisvaleurs valides :'typing'(initial),'sending', et'sent':
Vous pouvez toujours déclarer quelques constantes pour la lisibilité :
Mais ce ne sont pas des variables d'état, donc vous n'avez pas à vous soucier qu'elles se désynchronisent l'une de l'autre.
Éviter les états redondants
Si vous pouvez calculer certaines informations à partir des props du composant ou de ses variables d'état existantes pendant le rendu, vousne devriez pasmettre ces informations dans l'état de ce composant.
Par exemple, prenez ce formulaire. Il fonctionne, mais pouvez-vous y trouver un état redondant ?
Ce formulaire a trois variables d'état :firstName,lastName, etfullName. Cependant,fullNameest redondant.Vous pouvez toujours calculerfullNameà partir defirstNameetlastNamependant le rendu, donc retirez-le de l'état.
Voici comment vous pouvez le faire :
Ici,fullNamen'estpasune variable d'état. Au lieu de cela, il est calculé pendant le rendu :
Par conséquent, les gestionnaires de changement n'ont rien de spécial à faire pour le mettre à jour. Lorsque vous appelezsetFirstNameousetLastName, vous déclenchez un nouveau rendu, et le prochainfullNamesera calculé à partir des données fraîches.
Évitez la duplication dans l'état
Ce composant de liste de menus vous permet de choisir une seule collation de voyage parmi plusieurs :
Actuellement, il stocke l'élément sélectionné sous forme d'objet dans la variable d'étatselectedItem. Cependant, ce n'est pas idéal :le contenu deselectedItemest le même objet que l'un des éléments de la listeitems.Cela signifie que l'information sur l'élément lui-même est dupliquée à deux endroits.
Pourquoi est-ce un problème ? Rendons chaque élément modifiable :
Remarquez que si vous cliquez d'abord sur « Choose » pour un élément etensuiteque vous le modifiez,l'entrée se met à jour mais l'étiquette en bas ne reflète pas les modifications.C'est parce que vous avez dupliqué l'état et que vous avez oublié de mettre à jourselectedItem.
Bien que vous puissiez aussi mettre à jourselectedItem, une solution plus simple est de supprimer la duplication. Dans cet exemple, au lieu d'un objetselectedItem(qui crée une duplication avec les objets à l'intérieur deitems), vous conservez l'selectedIddans l'état, etensuitevous obtenez leselectedItemen recherchant dans le tableauitemsun élément avec cet ID :
L'état était dupliqué comme ceci :
items = [{ id: 0, title: 'pretzels'}, ...]selectedItem = {id: 0, title: 'pretzels'}
Mais après le changement, cela devient :
items = [{ id: 0, title: 'pretzels'}, ...]selectedId = 0
La duplication a disparu, et vous ne conservez que l'état essentiel !
Maintenant, si vous modifiez l'élémentsélectionné, le message ci-dessous se mettra à jour immédiatement. C'est parce quesetItemsdéclenche un nouveau rendu, etitems.find(...)trouverait l'élément avec le titre mis à jour. Vous n'aviez pas besoin de conserverl'élément sélectionnédans l'état, car seull'ID sélectionnéest essentiel. Le reste pouvait être calculé pendant le rendu.
Évitez les états profondément imbriqués
Imaginez un plan de voyage composé de planètes, de continents et de pays. Vous pourriez être tenté de structurer son état en utilisant des objets et des tableaux imbriqués, comme dans cet exemple :
Maintenant, disons que vous voulez ajouter un bouton pour supprimer un lieu que vous avez déjà visité. Comment vous y prendriez-vous ?Mettre à jour un état imbriquéimplique de créer des copies des objets tout le long de la chaîne parente à partir de la partie qui a changé. Supprimer un lieu profondément imbriqué impliquerait de copier toute sa chaîne parente. Un tel code peut être très verbeux.
Si l'état est trop imbriqué pour être mis à jour facilement, envisagez de le rendre « plat ».Voici une façon de restructurer ces données. Au lieu d'une structure arborescente où chaqueplacea un tableau deses lieux enfants, vous pouvez faire en sorte que chaque lieu contienne un tableau desID de ses lieux enfants. Ensuite, stockez une correspondance entre chaque ID de lieu et le lieu correspondant.
Cette restructuration des données pourrait vous rappeler une table de base de données :
Maintenant que l'état est « plat » (aussi appelé « normalisé »), la mise à jour des éléments imbriqués devient plus facile.
Pour supprimer un lieu maintenant, vous n'avez besoin de mettre à jour que deux niveaux d'état :
- La version mise à jour de son lieuparentdoit exclure l'ID supprimé de son tableau
childIds. - La version mise à jour de l'objet « table » racine doit inclure la version mise à jour du lieu parent.
Voici un exemple de la manière dont vous pourriez procéder :
Vous pouvez imbriquer l'état autant que vous le souhaitez, mais le rendre « plat » peut résoudre de nombreux problèmes. Cela facilite la mise à jour de l'état et aide à éviter les duplications dans différentes parties d'un objet imbriqué.
Parfois, vous pouvez également réduire l'imbrication de l'état en déplaçant une partie de l'état imbriqué vers les composants enfants. Cela fonctionne bien pour l'état d'interface utilisateur éphémère qui n'a pas besoin d'être stocké, comme le survol d'un élément.
Récapitulatif
- Si deux variables d'état sont toujours mises à jour ensemble, envisagez de les fusionner en une seule.
- Choisissez soigneusement vos variables d'état pour éviter de créer des états « impossibles ».
- Structurez votre état de manière à réduire les risques d'erreur lors de sa mise à jour.
- Évitez les états redondants et dupliqués pour ne pas avoir à les synchroniser.
- Ne mettez pas les propsdansl'état, sauf si vous souhaitez spécifiquement empêcher les mises à jour.
- Pour les modèles d'interface comme la sélection, conservez l'ID ou l'index dans l'état plutôt que l'objet lui-même.
- Si la mise à jour d'un état profondément imbriqué est compliquée, essayez de l'aplatir.
Try out some challenges
Challenge 1 of 4:Fix a component that’s not updating #
This Clock component receives two props: color and time. When you select a different color in the select box, the Clock component receives a different color prop from its parent component. However, for some reason, the displayed color doesn’t update. Why? Fix the problem.
