React shouldComponentUpdate démystifié

En développant dans React, vous êtes-vous déjà demandé quand et pourquoi la méthode render () d'un composant est exécutée? Ou quand utiliser des méthodes de cycle de vie moins évidentes shouldComponentUpdate ()?

Si la réponse est oui, votre application peut avoir des problèmes de performances. Lisez-les et vous pourrez les réparer facilement.

Tout se résume à la façon dont React fonctionne sous le capot. La grande promesse de React est qu'il est extrêmement rapide pour le rendu des éléments sur une page.

Pour ce faire, React garde en mémoire deux versions du DOM:

  • la version du DOM actuellement affichée
  • la prochaine version du DOM à afficher

Il compare les deux et met à jour le DOM affiché avec uniquement les parties qui ont changé. Ce processus s'appelle la réconciliation d'arbre. La racine de l'arborescence évaluée pour la réconciliation est un composant dont les accessoires ont changé.

Génial. Maintenant, que vous l'ayez prévu ou non, votre application Web suit dans une certaine mesure la division conteneur / composants de présentation. Voir ici et ici pour les définitions. Cela signifie que chaque vue complexe de votre application est constituée d'un composant conteneur qui contient la logique et contient de nombreux composants d'affichage uniquement en tant qu'enfants.

C'est un très bon modèle. Si vous regardez de plus près, cela signifie que toute interaction de l'utilisateur sur la vue affectera le conteneur lui-même et déclenchera un rendu de celui-ci et de tous ses enfants. Supposons que vous ayez une liste d'éléments avec un affichage fantaisie de texte, d'image et un bouton en forme d'étoile jaune «Ajouter aux favoris». Le modèle minimal pour un élément de liste pourrait être:

product = { imageUrl: '...', title: '...', isFavourite: false }

La liste des favoris pourrait provenir d'une autre source de données. Quoi qu'il en soit, l'organisation de vos composants ressemble probablement à ceci:

Le gestionnaire est appelé au clic de l'utilisateur et enregistre le côté serveur d'informations (ou persiste dans un magasin ou autre) et déclenche une modification dans this.props.elements.

Le résultat d'un simple clic déclenche le rendu du conteneur et de toutes les lignes de la liste juste pour mettre à jour une case à cocher.

C'est ici qu'intervient shouldComponentUpdate (). Vous pouvez dire à React de ne pas rendre les lignes qui n'ont pas besoin d'utiliser cette méthode.

class ListItem extends Component { shouldComponentUpdate(nextProps, nextState) { return nextProps.isFavourite != this.props.isFavourite; } ... }

Voici un cas concret: sur un projet d'application marketplace, nous avions une vue de gestion des produits pour les vendeurs. La liste avait un modèle «charger plus lorsque l'utilisateur fait défiler vers le bas» et une action d'élément en ligne «afficher / masquer» pour définir la visibilité de chaque produit. Tout allait bien lorsque les vendeurs géraient moins de 100 produits dans leur tableau de bord. Ensuite, un vendeur donné a commencé à saisir et à promouvoir plus de 300 produits…

Il y a eu un décalage d'environ 600 ms avant la mise à jour de l'interface utilisateur après qu'un utilisateur a cliqué sur l'icône «activer / désactiver». Le décalage était clairement visible par l'utilisateur final. En utilisant le profileur Chrome, nous avons vu qu'il fallait à React ~ 2 ms pour rendre une seule ligne. Fois 300… nous avons atteint 600 ms. Nous avons ajouté les vérifications shouldComponentUpdate () pour les conditions appropriées. Le temps de rendu après le clic de l'utilisateur est passé sous 10 ms…

J'ai monté un petit projet qui permet de reproduire ici ce cas. Exécutez-le et lisez les commentaires du code pour voir la magie opérer.

Avertissement pour les utilisateurs de Redux

Le problème décrit ci-dessus peut se produire plus souvent si vous utilisez Redux et resélectionnez (ou des bibliothèques de pipelines d'actions similaires «basées sur le magasin»).

Avec Redux et resélectionnez, vous poussez des actions vers le magasin et vous branchez des écouteurs pour stocker les modifications, c'est-à-dire des sélecteurs. Les sélecteurs sont disponibles dans le monde entier dans l'application et sur une grande application, il est assez facile pour de nombreux composants de se mapper aux mêmes sélecteurs. Les modifications apportées au magasin peuvent déclencher des modifications d'accessoires et donc des rendus qui ne sont absolument pas pertinents pour certains composants.

Voici le conseil déroutant: n'utilisez pas shouldComponentUpdate () pour empêcher les rendus dans de tels cas. La logique à l'intérieur de shouldComponentUpdate ne doit examiner que ce qui est pertinent pour le composant. Il ne doit jamais anticiper les contextes dans lesquels le composant est utilisé. La raison en est simplement que votre code deviendrait rapidement impossible à maintenir.

Si vous rencontrez ce genre de problèmes, cela signifie que la structure de votre magasin est incorrecte ou que les sélecteurs ne sont pas assez spécifiques. Vous devez vous rendre à un nouveau cycle de modélisation.

Je recommande ces superbes directives passe-partout. Il favorise l'encapsulation du magasin par conteneur de haut niveau avec une zone globale pour les structures de données clés qui s'étendent sur l'ensemble de l'application. Il s'agit d'une approche assez sûre pour éviter les erreurs de modélisation de magasin.

Merci d'avoir lu! Si vous l'avez aimé, cliquez sur le bouton clap ci-dessous. Cela aide d'autres personnes à voir l'histoire.