Comment accélérer une application React avec le co-hébergement d'état

L'article concerne la colocation d'État , c'est-à-dire le placement conjoint d'États , ce terme pourrait encore être traduit par colocation d'État ou colocation d'État .


L'une des principales raisons du ralentissement d'une application React est son état global. Je vais le montrer avec un exemple d'application très simple, après quoi je donnerai un exemple plus proche de la vie réelle.


Voici une application simple dans laquelle vous pouvez saisir un nom pour le chien (si la fenêtre ne fonctionne pas, voici le lien ):



Si vous jouez avec cette application, vous constaterez bientôt qu'elle fonctionne très lentement. Lors de l'interaction avec n'importe quel champ de saisie, il existe des problèmes de performances notables. Dans une telle situation, vous pouvez utiliser une bouée de sauvetage sous la forme de React.memo et l'envelopper avec tous les composants avec un rendu lent. Mais essayons de résoudre ce problème différemment.


Voici le code de cette application:


 function sleep(time) { const done = Date.now() + time while (done > Date.now()) { // ... } } //      // -       function SlowComponent({time, onChange}) { sleep(time) return ( <div> Wow, that was{' '} <input value={time} type="number" onChange={e => onChange(Number(e.target.value))} /> ms slow </div> ) } function DogName({time, dog, onChange}) { return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [dog, setDog] = React.useState('') const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} dog={dog} onChange={setDog} /> <SlowComponent time={time} onChange={setTime} /> </div> ) } 

Si vous n'avez pas lu l'article sur la colocation ( colocation ), je vous suggère de le lire. Sachant que le co-hébergement peut faciliter le travail avec notre application, utilisons ce principe lorsque vous travaillez avec des états.


Faites attention au code de notre application, à savoir l'état time - il est utilisé par chaque composant de notre application, donc il a été levé ( lever - augmenter l'état ) vers le composant App (le composant qui enveloppe toute notre application). Cependant, l'état "dog" ( dog et setDog ) n'est utilisé que par un seul composant, il n'est pas nécessaire dans le composant App , nous allons donc le déplacer vers le composant DogName :


 function DogName({time}) { // <-    const [dog, setDog] = React.useState('') // <-   return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} /> // <-    <SlowComponent time={time} onChange={setTime} /> </div> ) } 

Et voici notre résultat (si la fenêtre ne fonctionne pas, lien ):



Ouah! La saisie d'un nom fonctionne désormais beaucoup plus rapidement. De plus, le composant est devenu plus facile à entretenir grâce à la colocalisation. Mais pourquoi cela a-t-il fonctionné plus rapidement?


Ils disent que la meilleure façon de faire quelque chose rapidement est de faire le moins de choses possible. C'est exactement ce qui se passe ici. Lorsque nous gérons l'état situé tout en haut de l'arborescence des composants, chaque mise à jour de cet état invalidera l'arborescence entière. Le React ne sait pas ce qui a changé, pour cette raison, il doit vérifier tous les composants pour voir s'ils ont besoin de mises à jour DOM. Ce processus n'est pas gratuit et consomme des ressources (surtout si vous avez des composants intentionnellement lents). Mais si vous déplacez l'état le plus bas possible dans l'arborescence des composants, comme nous l'avons fait avec l'état dog et le composant DogName , React effectuera moins de vérifications. Le React ne vérifiera pas le composant SlowComponent (que nous avons rendu délibérément lent), car React sait que ce composant ne peut toujours pas affecter la sortie.


En bref, plus tôt, lors du changement du nom du chien, chaque composant a été vérifié pour les changements (re-rendus). Et après les modifications que nous avons apportées au code, React a commencé à vérifier uniquement le composant DogName . Cela a conduit à une nette augmentation de la productivité!


Dans la vraie vie


Je vois que les développeurs mettent dans le référentiel global Redux ou dans le contexte global ce qui ne devrait vraiment pas être global. DogName telles que le DogName de l'exemple ci-dessus sont souvent des endroits où un problème de performances se produit. Je vois souvent que ce problème se manifeste lors de l'interaction avec la souris (par exemple, lors de l'affichage d'une info-bulle au-dessus d'un graphique ou au-dessus d'un tableau de données).


Une solution à ce problème consiste à "annuler" l'interaction utilisateur (c'est-à-dire que nous attendons que l'utilisateur arrête de taper, puis nous appliquons la mise à jour de l'état). Parfois, c'est le mieux que nous pouvons faire, mais cela peut conduire à une mauvaise expérience utilisateur (le futur mode simultané devrait minimiser la nécessité de le faire. Voir cette démo de Dan Abramov ).


Une autre solution que les développeurs utilisent souvent consiste à utiliser l'un des rendus de sauvetage React, par exemple React.memo . Cela fonctionnera très bien dans notre exemple tiré par les cheveux, car il permet au React d'ignorer le nouveau rendu du SlowComponent , mais dans la pratique, l'application peut souffrir en raison de la «mort de mille coupures», car dans une application réelle, le ralentissement n'est généralement pas dû à un lent et en raison du travail insuffisamment rapide de nombreux composants, vous devez donc utiliser React.memo partout. Cela fait, vous devrez commencer à utiliser useMemo et useCallback , sinon tout le travail que vous mettrez dans React.memo sera en vain. Ces actions peuvent résoudre le problème, mais elles augmentent considérablement la complexité de votre code et, en fait, elles sont toujours moins efficaces que les états de partage, car React doit passer par chaque composant (en partant du haut) pour déterminer s'il doit être rendu à nouveau.


Si vous voulez jouer avec un exemple un peu moins farfelu, allez à codesandbox ici .


Qu'est-ce que la colocalisation?


Le principe du placement conjoint énonce:


Le code doit être situé le plus près possible du lieu auquel il se rapporte.

Donc, afin de respecter ce principe, notre état de dog doit être à l' intérieur du composant DogName :


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } 

Mais que se passe-t-il si nous divisons ce composant en plusieurs composants? Où l'État devrait-il aller? La réponse est la même: "le plus près possible de l'endroit auquel elle se rapporte" et telle sera la composante parente commune la plus proche . Par exemple, décomposons le composant DogName que l' input et p affichés dans des composants différents:


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <DogInput dog={dog} onChange={setDog} /> <DogFavoriteNumberDisplay time={time} dog={dog} /> </div> ) } function DogInput({dog, onChange}) { return ( <> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> </> ) } function DogFavoriteNumberDisplay({time, dog}) { return ( <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> ) } 

Nous ne pouvons pas déplacer l'état vers le composant DogInput , car le composant DogFavoriteNumberDisplay également besoin d'accéder à l'état, nous allons donc de bas en haut de l'arborescence des composants jusqu'à ce que nous trouvions le parent commun de ces deux composants et y organisions la gestion des états.


Tout cela s'applique aux situations où vous devez contrôler l'état de dizaines de composants sur un écran particulier de votre application. Vous pouvez même déplacer cela dans le contexte pour éviter le forage de prop si vous le souhaitez. Mais gardez ce contexte aussi près que possible de l'endroit auquel il appartient, et vous continuerez ensuite à maintenir les bonnes performances (et la convivialité du code) que le partage fournit. N'oubliez pas que vous n'avez pas besoin de placer tous les contextes au niveau supérieur de votre application React. Gardez-les là où cela a le plus de sens.


C'est l'idée principale de mon autre article, Application State Management with React . Gardez vos états aussi près que possible de l'endroit où ils sont utilisés, cela améliorera à la fois les performances et la convivialité du code. Avec cette approche, la seule chose qui pourrait éventuellement aggraver les performances de votre application est des interactions particulièrement complexes avec les éléments d'interface.


Alors quoi utiliser, contextes ou Redux?


Si vous lisez «Une astuce simple pour optimiser les rendus de React» , vous savez que vous pouvez vous assurer que seuls les composants qui utilisent l'état modifié sont mis à jour. De cette façon, vous pouvez simplement contourner ce problème. Mais les gens rencontrent toujours des problèmes de performances lorsqu'ils utilisent l'éditeur. Le problème est que React-Redux s'attend à ce que vous suiviez ses recommandations pour éviter un rendu inutile des composants connectés . Il y a toujours un risque d'erreur; vous pouvez accidentellement configurer des composants afin qu'ils commencent à s'afficher trop souvent lorsque d'autres états globaux changent. Et plus votre application est grande, plus l'effet sera négatif, surtout si vous ajoutez trop d'états à l'éditeur.


Il existe des méthodes qui peuvent vous aider à éviter ces problèmes, par exemple, utiliser des memoized Reselect mapState ou lire la documentation des éditeurs pour plus d'informations sur l'amélioration des performances des applications .


Il convient de noter que le co-placement peut être appliqué avec l'éditeur. N'utilisez les éditeurs que pour les états globaux et pour tout le reste, utilisez la colocalisation. La FAQ de l' éditeur contient des règles utiles pour vous aider à décider si l'état doit fonctionner dans l'éditeur ou s'il doit rester dans le composant .


Soit dit en passant, si vous divisez votre état en domaines (en utilisant plusieurs contextes selon le domaine), le problème sera moins prononcé.


Mais le fait demeure: la colocalisation des états réduit les problèmes de performances et simplifie la maintenance du code.


Décidez où mettre l'État


Arbre de décision:


où mettre l'état


Version texte, s'il n'y a aucun moyen de regarder l'image:


  • 1 Nous commençons le développement de l'application. Allez à 2
  • 2 Statut dans le composant. Allez à 3
  • 3 La condition est-elle utilisée uniquement par ce composant?
    • Hein? On passe à 4
    • Non? Cet état a-t-il besoin d' un seul composant enfant?
    • Hein? Déplacez-le vers ce composant enfant (utilisez la co-localisation). Nous allons à 3.
    • Non? Cet état a-t-il besoin de composants parents ou voisins (composants "frères", c'est-à-dire enfants du même composant parent)?
      • Hein? Déplacez l'état ci-dessus vers le composant parent. Allez à 3
      • Non? On passe à 4
  • 4 Laissez tel quel. Allez à 5
  • 5 Y a-t-il un problème avec le forage par hélice?
    • Hein? Nous déplaçons cet état vers le fournisseur de contexte et rendons ce fournisseur dans le composant dans lequel l'état est géré. Passez à 6
    • Non? Passez à 6
  • 6 Nous envoyons la demande. Lorsque de nouvelles exigences apparaissent, passez à 1

Il est important que ce processus fasse partie de votre processus régulier de refactorisation / maintenance des applications. Si votre condition n'est pas élevée là où elle devrait être élevée, alors cet état cessera de fonctionner correctement et vous le remarquerez. Toutefois, si vous ne suivez pas la méthode de colocalisation des états et que vous n'abaissez pas les états plus bas dans la hiérarchie des composants, votre application continuera de fonctionner. De ce fait, vous ne remarquerez pas immédiatement les problèmes de performances et de gestion qui s'accumuleront progressivement.


Conclusion


En général, les développeurs comprennent très bien quand il est nécessaire d'augmenter l'état ("état de levage") quand cela est nécessaire, mais nous ne comprenons pas bien quand l'état doit être abaissé. Je vous suggère de jeter un coup d'œil au code de votre application et de réfléchir à l'endroit où l'état pourrait être omis en appliquant le principe de la «co-localisation». Demandez-vous: " isOpen -je besoin de l'état isOpen d'une fenêtre modale dans l'éditeur?" (la réponse est très probablement non).


Utilisez le principe de la co-localisation et votre code deviendra plus facile et plus rapide.


Bonne chance

Source: https://habr.com/ru/post/fr485032/


All Articles