Composants React efficaces : un guide pour optimiser les performances de React

Publié: 2022-03-11

Depuis son introduction, React a changé la façon dont les développeurs frontaux pensent à la création d'applications Web. Avec le DOM virtuel, React rend les mises à jour de l'interface utilisateur aussi efficaces que possible, ce qui rend votre application Web plus rapide. Mais pourquoi les applications Web React de taille moyenne ont-elles encore tendance à mal fonctionner ?

Eh bien, l'indice réside dans la façon dont vous utilisez React.

Une bibliothèque frontale moderne comme React ne rend pas votre application plus rapide par magie. Cela nécessite que le développeur comprenne comment fonctionne React et comment les composants traversent les différentes phases du cycle de vie des composants.

Avec React, vous pouvez obtenir une grande partie des améliorations de performances qu'il a à offrir en mesurant et en optimisant comment et quand vos composants s'affichent. Et, React fournit uniquement les outils et fonctionnalités nécessaires pour rendre cela facile.

Accélérez votre application React en optimisant le processus de rendu de vos composants.

Dans ce didacticiel React, vous apprendrez comment mesurer les performances de vos composants React et les optimiser pour créer une application Web React beaucoup plus performante. Vous apprendrez également comment quelques bonnes pratiques JavaScript aident également à faire en sorte que votre application Web React offre une expérience utilisateur beaucoup plus fluide.

Comment fonctionne React ?

Avant de nous plonger dans les techniques d'optimisation, nous devons mieux comprendre le fonctionnement de React.

Au cœur du développement de React, vous avez la syntaxe JSX simple et évidente, et la capacité de React à construire et comparer des DOM virtuels. Depuis sa sortie, React a influencé de nombreuses autres bibliothèques frontales. Des bibliothèques telles que Vue.js reposent également sur l'idée de DOM virtuels.

Voici comment fonctionne React :

Chaque application React commence par un composant racine et est composée de nombreux composants dans une arborescence. Les composants de React sont des « fonctions » qui rendent l'interface utilisateur en fonction des données (accessoires et état) qu'elle reçoit.

Nous pouvons symboliser cela comme F .

 UI = F(data)

Les utilisateurs interagissent avec l'interface utilisateur et provoquent la modification des données. Que l'interaction implique de cliquer sur un bouton, d'appuyer sur une image, de faire glisser des éléments de liste, des requêtes AJAX appelant des API, etc., toutes ces interactions ne font que modifier les données. Ils ne font jamais changer directement l'interface utilisateur.

Ici, les données sont tout ce qui définit l'état de l'application Web, et pas seulement ce que vous avez stocké dans votre base de données. Même des bits d'états frontaux (par exemple, quel onglet est actuellement sélectionné ou si une case à cocher est actuellement cochée) font partie de ces données.

Chaque fois qu'il y a un changement dans ces données, React utilise les fonctions du composant pour restituer l'interface utilisateur, mais seulement virtuellement :

 UI1 = F(data1) UI2 = F(data2)

React calcule les différences entre l'UI actuelle et la nouvelle UI en appliquant un algorithme de comparaison sur les deux versions de son DOM virtuel.

 Changes = Diff(UI1, UI2)

React applique ensuite uniquement les modifications de l'interface utilisateur à la véritable interface utilisateur du navigateur.

Lorsque les données associées à un composant changent, React détermine si une mise à jour réelle du DOM est requise. Cela permet à React d'éviter des opérations de manipulation DOM potentiellement coûteuses dans le navigateur, telles que la création de nœuds DOM et l'accès à ceux existants au-delà de la nécessité.

Cette différence et ce rendu répétés des composants peuvent être l'une des principales sources de problèmes de performances de React dans n'importe quelle application React. La création d'une application React où l'algorithme différent ne parvient pas à se réconcilier efficacement, ce qui entraîne le rendu répété de l'intégralité de l'application peut entraîner une expérience frustrante et lente.

Par où commencer l'optimisation ?

Mais qu'est-ce que nous optimisons exactement ?

Vous voyez, pendant le processus de rendu initial, React construit un arbre DOM comme celui-ci :

Un DOM virtuel de composants React

Compte tenu d'une partie des modifications de données, ce que nous voulons que React fasse, c'est restituer uniquement les composants qui sont directement affectés par la modification (et éventuellement ignorer même le processus de comparaison pour le reste des composants) :

React rendant un nombre optimal de composants

Cependant, ce que React finit par faire est :

Réagissez en gaspillant des ressources en rendant tous les composants

Dans l'image ci-dessus, tous les nœuds jaunes sont rendus et différenciés, ce qui entraîne une perte de temps/ressources de calcul. C'est là que nous allons principalement concentrer nos efforts d'optimisation. Configurer chaque composant pour ne rendre le rendu que lorsque cela est nécessaire nous permettra de récupérer ces cycles CPU gaspillés.

Les développeurs de la bibliothèque React ont pris cela en considération et nous ont fourni un crochet pour faire exactement cela : une fonction qui nous permet de dire à React quand il est correct de sauter le rendu d'un composant.

Mesurer d'abord

Comme Rob Pike le dit assez élégamment comme l'une de ses règles de programmation :

Mesure. Ne réglez pas la vitesse tant que vous n'avez pas mesuré, et même dans ce cas, ne le faites pas à moins qu'une partie du code ne submerge le reste.

Ne commencez pas à optimiser le code qui, selon vous, pourrait ralentir votre application. Au lieu de cela, laissez les outils de mesure des performances de React vous guider tout au long du processus.

React a un outil puissant juste pour cela. En utilisant la bibliothèque react-addons-perf , vous pouvez obtenir un aperçu des performances globales de votre application.

L'utilisation est très simple :

 Import Perf from 'react-addons-perf' Perf.start(); // use the app Perf.stop(); Perf.printWasted();

Cela imprimera un tableau avec la quantité de composants de temps perdus dans le rendu.

Tableau des composants perdant du temps en rendu

La bibliothèque fournit d'autres fonctions qui vous permettent d'imprimer séparément différents aspects du temps perdu (par exemple, en utilisant les printInclusive() ou printExclusive() ), ou même d'imprimer les opérations de manipulation du DOM (en utilisant la fonction printOperations() ).

Aller plus loin dans l'analyse comparative

Si vous êtes une personne visuelle, alors react-perf-tool est exactement ce dont vous avez besoin.

react-perf-tool est basé sur la bibliothèque react-addons-perf . Cela vous donne un moyen plus visuel de déboguer les performances de votre application React. Il utilise la bibliothèque sous-jacente pour obtenir des mesures, puis les visualise sous forme de graphiques.

Une visualisation des composants perdant du temps en rendu

Le plus souvent, c'est un moyen beaucoup plus pratique de repérer les goulots d'étranglement. Vous pouvez l'utiliser facilement en l'ajoutant en tant que composant à votre application.

React devrait-il mettre à jour le composant ?

Par défaut, React s'exécutera, restituera le DOM virtuel et comparera la différence pour chaque composant de l'arborescence pour tout changement dans ses accessoires ou son état. Mais ce n'est évidemment pas raisonnable.

Au fur et à mesure que votre application grandit, tenter de restituer et de comparer l'intégralité du DOM virtuel à chaque action finira par ralentir.

React fournit au développeur un moyen simple d'indiquer si un composant doit être rendu à nouveau. C'est là que la méthode shouldComponentUpdate entre en jeu.

 function shouldComponentUpdate(nextProps, nextState) { return true; }

Lorsque cette fonction renvoie true pour n'importe quel composant, elle permet au processus de rendu de différenciation d'être déclenché.

Cela vous donne un moyen simple de contrôler le processus de rendu-diff. Chaque fois que vous avez besoin d'empêcher un composant d'être rendu à nouveau, renvoyez simplement false à partir de la fonction. À l'intérieur de la fonction, vous pouvez comparer l'ensemble actuel et suivant d'accessoires et d'état pour déterminer si un nouveau rendu est nécessaire :

 function shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id; }

Utilisation d'un React.PureComponent

Pour faciliter et automatiser un peu cette technique d'optimisation, React fournit ce qu'on appelle un composant "pur". Un React.PureComponent est exactement comme un React.Component qui implémente une fonction shouldComponentUpdate() avec une prop peu profonde et une comparaison d'état.

Un React.PureComponent est plus ou moins équivalent à ceci :

 class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this.props, nextProps) && shallowCompare(this.state, nextState); } … }

Comme il n'effectue qu'une comparaison superficielle, vous ne le trouverez utile que lorsque :

  • Vos accessoires ou états contiennent des données primitives.
  • Vos accessoires et vos états contiennent des données complexes, mais vous savez quand appeler forceUpdate() pour mettre à jour votre composant.

Rendre les données immuables

Et si vous pouviez utiliser un React.PureComponent tout en ayant un moyen efficace de savoir quand des accessoires ou des états complexes ont changé automatiquement ? C'est là que les structures de données immuables facilitent la vie.

L'idée derrière l'utilisation de structures de données immuables est simple. Chaque fois qu'un objet contenant des données complexes change, au lieu d'apporter les modifications à cet objet, créez une copie de cet objet avec les modifications. Cela rend la détection des changements dans les données aussi simple que la comparaison de la référence des deux objets.

Vous pouvez utiliser Object.assign ou _.extend (de Underscore.js ou Lodash) :

 const newValue2 = Object.assign({}, oldValue); const newValue2 = _.extend({}, oldValue);

Mieux encore, vous pouvez utiliser une bibliothèque qui fournit des structures de données immuables :

 var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 2); assert(map1.equals(map2) === true); var map3 = map1.set('b', 50); assert(map1.equals(map3) === false);

Ici, Immutable.Map est fourni par la bibliothèque Immutable.js.

Chaque fois qu'une carte est mise à jour avec sa méthode set , une nouvelle carte est renvoyée uniquement si l'opération set a modifié la valeur sous-jacente. Sinon, la même carte est renvoyée.

Vous pouvez en savoir plus sur l'utilisation de structures de données immuables ici.

Plus de techniques d'optimisation des applications React

Utilisation de la version de production

Lors du développement d'une application React, des avertissements et des messages d'erreur très utiles vous sont présentés. Ceux-ci font de l'identification des bogues et des problèmes pendant le développement un bonheur. Mais ils ont un coût de performance.

Si vous examinez le code source de React, vous verrez de nombreuses vérifications if (process.env.NODE_ENV != 'production') . Ces morceaux de code que React exécute dans votre environnement de développement ne sont pas nécessaires à l'utilisateur final. Pour les environnements de production, tout ce code inutile peut être supprimé.

Si vous avez démarré votre projet à l'aide create-react-app , vous pouvez simplement exécuter npm run build pour produire la version de production sans ce code supplémentaire. Si vous utilisez Webpack directement, vous pouvez exécuter webpack -p (qui équivaut à webpack --optimize-minimize --define process.env.NODE_ENV="'production'" .

Fonctions de liaison précoces

Il est très courant de voir des fonctions liées au contexte du composant à l'intérieur de la fonction de rendu. C'est souvent le cas lorsque nous utilisons ces fonctions pour gérer les événements des composants enfants.

 // Creates a new `handleUpload` function during each render() <TopBar onUpload={this.handleUpload.bind(this)} /> // ...as do inlined arrow functions <TopBar onUpload={files => this.handleUpload(files)} />

Cela entraînera la fonction render() pour créer une nouvelle fonction sur chaque rendu. Une bien meilleure façon de faire la même chose est:

 class App extends React.Component { constructor(props) { super(props); this.handleUpload = this.handleUpload.bind(this); } render() { … <TopBar onUpload={this.handleUpload} /> … } }

Utilisation de plusieurs fichiers de blocs

Pour les applications Web React d'une seule page, nous finissons souvent par regrouper tout notre code JavaScript frontal dans un seul fichier minifié. Cela fonctionne bien pour les applications Web de petite à moyenne taille. Mais à mesure que l'application commence à se développer, la livraison de ce fichier JavaScript groupé au navigateur en lui-même peut devenir un processus chronophage.

Si vous utilisez Webpack pour créer votre application React, vous pouvez tirer parti de ses capacités de fractionnement de code pour séparer le code de votre application intégrée en plusieurs «morceaux» et les transmettre au navigateur au besoin.

Il existe deux types de fractionnement : le fractionnement des ressources et le fractionnement du code à la demande.

Avec le fractionnement des ressources, vous divisez le contenu des ressources en plusieurs fichiers. Par exemple, en utilisant CommonsChunkPlugin, vous pouvez extraire du code commun (comme toutes les bibliothèques externes) dans un fichier « bloc » qui lui est propre. En utilisant ExtractTextWebpackPlugin, vous pouvez extraire tout le code CSS dans un fichier CSS séparé.

Ce type de fractionnement aidera de deux manières. Cela aide le navigateur à mettre en cache ces ressources qui changent moins fréquemment. Cela aidera également le navigateur à tirer parti du téléchargement parallèle pour potentiellement réduire le temps de chargement.

Une caractéristique plus notable de Webpack est le fractionnement de code à la demande. Vous pouvez l'utiliser pour diviser le code en un morceau qui peut être chargé à la demande. Cela peut réduire le téléchargement initial, ce qui réduit le temps nécessaire au chargement de l'application. Le navigateur peut ensuite télécharger d'autres morceaux de code à la demande lorsque l'application en a besoin.

Vous pouvez en savoir plus sur le fractionnement de code Webpack ici.

Activation de Gzip sur votre serveur Web

Les fichiers JS du bundle de l'application React sont généralement très volumineux, donc pour accélérer le chargement de la page Web, nous pouvons activer Gzip sur le serveur Web (Apache, Nginx, etc.)

Les navigateurs modernes prennent tous en charge et négocient automatiquement la compression Gzip pour les requêtes HTTP. L'activation de la compression Gzip peut réduire la taille de la réponse transférée jusqu'à 90 %, ce qui peut réduire considérablement le temps de téléchargement de la ressource, réduire l'utilisation des données pour le client et améliorer le délai de premier rendu de vos pages.

Consultez la documentation de votre serveur Web pour savoir comment activer la compression :

  • Apache : utilisez mod_deflate
  • Nginx : utilisez ngx_http_gzip_module

Utiliser Eslint-plugin-react

Vous devriez utiliser ESLint pour presque tous les projets JavaScript. Réagir n'est pas différent.

Avec eslint-plugin-react , vous vous forcerez à vous adapter à de nombreuses règles de programmation React qui peuvent bénéficier à long terme à votre code et éviter de nombreux problèmes et problèmes courants qui surviennent en raison d'un code mal écrit.

Rendez à nouveau vos applications Web React rapides

Pour tirer le meilleur parti de React, vous devez tirer parti de ses outils et techniques. La performance d'une web app React réside dans la simplicité de ses composants. Surcharger l'algorithme de rendu-diff peut rendre votre application peu performante de manière frustrante.

Avant de pouvoir optimiser votre application, vous devez comprendre le fonctionnement des composants React et leur rendu dans le navigateur. Les méthodes de cycle de vie React vous permettent d'éviter que votre composant ne soit restitué inutilement. Éliminez ces goulots d'étranglement et vous bénéficierez des performances de l'application que vos utilisateurs méritent.

Bien qu'il existe d'autres façons d'optimiser une application Web React, le réglage fin des composants pour les mettre à jour uniquement lorsque cela est nécessaire permet d'améliorer au mieux les performances.

Comment mesurez-vous et optimisez-vous les performances de votre application Web React ? Partagez-le dans les commentaires ci-dessous.

En relation : Récupération de données obsolètes pendant la revalidation avec React Hooks : un guide