Avec la vie, la mort, le destin et les impôts, le comportement de rendu de React est l'une des plus grandes vérités et mystères de la vie.
Plongeons dedans!
Comme tout le monde, j'ai commencé mon parcours de développement front-end avec jQuery. La manipulation DOM purement basée sur JS était un cauchemar à l'époque, donc c'était ce que tout le monde faisait. Puis lentement, Framework basés sur JavaScript est devenu si important que je ne pouvais plus les ignorer.
Le premier que j'ai appris était Vue. J'ai eu une période incroyablement difficile parce que les composants et l'état et tout le reste étaient un modèle mental totalement nouveau, et c'était très pénible de tout intégrer. Mais finalement, je l'ai fait, et je me suis tapoté dans le dos. Félicitations mon pote, me dis-je, tu as fait la montée raide; maintenant, le reste des frameworks, si jamais vous avez besoin de les apprendre, sera très facile.
Alors, un jour, quand j'ai commencé apprendre à réagir, J'ai réalisé à quel point j'avais tort. Facebook n'a pas rendu les choses plus faciles en lançant des Hooks et en disant à tout le monde: «Hé, utilisez ceci à partir de maintenant. Mais ne réécrivez pas les classes; les cours sont bien. En fait, pas tellement, mais ça va. Mais les Hooks sont tout, et ils sont l'avenir.
Je l'ai? Génial!".
Finalement, j'ai traversé cette montagne aussi. Mais ensuite, j'ai été frappé par quelque chose d'aussi important et difficile que React lui-même: rendu.

Si vous êtes tombé sur le rendu et ses mystères dans React, vous savez de quoi je parle. Et si vous ne l'avez pas fait, vous n'avez aucune idée de ce qui vous attend! 😂
Mais avant de perdre du temps sur quoi que ce soit, c'est une bonne habitude de se demander ce que vous en gagneriez (contrairement à moi, qui est un idiot surexcité et qui apprendra avec plaisir n'importe quoi juste pour le plaisir 😭😭). Si votre vie en tant que développeur React se déroule bien sans vous soucier de ce qu'est ce rendu, pourquoi s'en soucier? Bonne question, répondons donc d'abord à cette question, puis nous verrons ce qu'est réellement le rendu.
Why is understanding rendering behavior in React important?
Nous commençons tous à apprendre React en écrivant (de nos jours, fonctionnels) des composants qui renvoient quelque chose appelé JSX. Nous comprenons également que ce JSX est en quelque sorte converti en éléments HTML DOM réels qui apparaissent sur la page. Les pages se mettent à jour au fur et à mesure que l'état se met à jour, les itinéraires changent comme prévu et tout va bien. Mais cette vision du fonctionnement de React est naïve et source de nombreux problèmes.
Bien que nous réussissions souvent à écrire des applications complètes basées sur React, nous trouvons parfois certaines parties de notre application (ou de l'application entière) remarquablement lentes. Et le pire. . . nous n'avons pas la moindre idée de pourquoi! Nous avons tout fait correctement, nous ne voyons aucune erreur ni avertissement, nous avons suivi toutes les bonnes pratiques de conception de composants, de normes de codage, etc. 🤔
Parfois, c'est un problème totalement différent: il n'y a rien de mal avec les performances, mais l'application se comporte bizarrement. Par exemple, effectuer trois appels d'API vers le backend d'authentification mais un seul vers tous les autres. Ou certaines pages sont redessinées deux fois, la transition visible entre les deux rendus de la même page créant un UX discordant.

Pire encore, il n'y a pas d'aide externe disponible dans de tels cas. Si vous allez sur votre forum de développement préféré et posez cette question, ils répondront: «Je ne peux pas le dire sans regarder votre application. Pouvez-vous joindre un exemple de travail minimum ici? » Eh bien, bien sûr, vous ne pouvez pas joindre l'application entière pour des raisons juridiques, alors qu'un petit exemple fonctionnel de cette partie peut ne pas contenir ce problème car il n'interagit pas avec l'ensemble du système tel qu'il est dans l'application réelle.
Vissé? Ouais, si tu me demandes. 🤭🤭
Donc, à moins que vous ne vouliez voir de tels jours de malheur, je vous suggère de développer une compréhension - et un intérêt, je dois insister; la compréhension acquise à contrecœur ne vous mènera pas loin dans le monde de React - dans cette chose mal comprise appelée rendu dans React. Croyez-moi, ce n'est pas si difficile à comprendre, et bien que ce soit très difficile à maîtriser, vous irez vraiment loin sans avoir à connaître tous les coins et recoins.
What does rendering mean in React?
C'est, mon ami, une excellente question. Nous n'avons pas tendance à le demander lorsque nous apprenons React (je le sais parce que je ne l'ai pas fait) parce que le mot "rendre" nous berce peut-être dans un faux sentiment de familiarité. Bien que la signification du dictionnaire soit complètement différente (et ce n'est pas important dans cette discussion), nous, les programmeurs, avons déjà une notion de ce que cela devrait signifier. Travailler avec des écrans, des API 3D, des cartes graphiques et lire les spécifications des produits entraîne notre esprit à penser à quelque chose comme "peindre une image" lorsque nous lisons le mot "rendre". Dans la programmation du moteur de jeu, il y a un moteur de rendu, dont le seul travail est de - précisément !, peindre le monde tel que remis par la scène.
Nous pensons donc que lorsque React «rend» quelque chose, il collecte tous les composants et repeint le DOM de la page Web. Mais dans le monde React (et oui, même dans la documentation officielle), ce n'est pas de cela qu'il s'agit. Alors, serrons nos ceintures de sécurité et plongeons-nous dans les composants internes de React.

Vous devez avoir entendu dire que React maintient ce qu'on appelle un DOM virtuel et qu'il le compare périodiquement avec le DOM réel et applique les modifications si nécessaire (c'est pourquoi vous ne pouvez pas simplement ajouter jQuery et React ensemble - React doit prendre le contrôle total de le DOM). Maintenant, ce DOM virtuel n'est pas composé d'éléments HTML comme le fait le vrai DOM, mais d'éléments React. Quelle est la différence? Bonne question! Pourquoi ne pas créer une petite application React et voir par nous-mêmes?
j'ai crée à cette application React très simple à cet effet. Le code entier est juste un seul fichier contenant quelques lignes:
import React from "react";
import "./styles.css";
export default function App() {
const element = (
<div className="App">
<h1>Hello, there!</h1>
<h2>Let's take a look inside React elements</h2>
</div>
);
console.log(element);
return element;
}
Remarquez ce que nous faisons ici?
Oui, il suffit de consigner à quoi ressemble un élément JSX. Ces expressions et composants JSX sont quelque chose que nous avons écrit des centaines de fois, mais nous prêtons rarement attention à ce qui se passe. Si vous ouvrez la console de développement de votre navigateur et exécutez cette application, vous verrez un Object
qui s'étend à:

Cela peut sembler intimidant, mais prenez note de quelques détails intéressants:
- Ce que nous recherchons est un objet JavaScript simple et régulier et non un nœud DOM.
- Notez que la propriété
props
dit qu'il a unclassName
ofApp
(qui est la classe CSS définie dans le code) et que cet élément a deux enfants (cela correspond aussi, les éléments enfants étant le<h1>
et<h2>
Mots clés). - La solution
_source
La propriété nous dit où le code source commence le corps de l'élément. Comme vous pouvez le voir, il nomme le fichierApp.js
comme source et mentionne la ligne numéro 6. Si vous regardez à nouveau le code, vous constaterez que la ligne 6 est juste après la balise JSX d'ouverture, ce qui est logique. Les parenthèses JSX contiennent l'élément React; ils n'en font pas partie, car ils servent à se transformer enReact.createElement()
Appelle plus tard. - La solution
__proto__
property nous dit que cet objet dérive tout son. propriétés du JavaScript racineObject
, renforçant à nouveau l'idée que ce ne sont que des objets JavaScript quotidiens que nous examinons ici.
Donc, maintenant, nous comprenons que le soi-disant DOM virtuel ne ressemble en rien au DOM réel mais est un arbre d'objets React (JavaScript) représentant l'interface utilisateur à ce moment-là.

Épuisé?
Croyez-moi, moi aussi. 🙂 Retourner ces idées encore et encore dans ma tête pour essayer de les présenter de la meilleure façon possible, puis penser aux mots pour les faire ressortir et les réorganiser - n'est pas facile. 😫
Mais nous sommes distraits!
Ayant survécu jusqu'ici, nous sommes maintenant en mesure de répondre à la question que nous recherchions: qu'est-ce que le rendu dans React?
Eh bien, le rendu est le processus du moteur React parcourant le DOM virtuel et collectant l'état actuel, les accessoires, la structure, les changements souhaités dans l'interface utilisateur, etc. React met maintenant à jour le DOM virtuel à l'aide de certains calculs et compare également le nouveau résultat avec le DOM réel sur la page. Ce calcul et cette comparaison sont ce que l'équipe React appelle officiellement «réconciliation», et si vous êtes intéressé par leurs idées et leurs algorithmes pertinents, vous pouvez consulter le site officiel docs.
Time to Commit!
Une fois la partie rendu terminée, React lance une phase appelée «commit», au cours de laquelle il applique les modifications nécessaires au DOM. Ces modifications sont appliquées de manière synchrone (l'une après l'autre, bien qu'un nouveau mode fonctionnant simultanément soit attendu prochainement), et le DOM est mis à jour. Le moment exact et la manière dont React applique ces changements ne sont pas notre préoccupation, car c'est quelque chose qui est totalement sous le capot et qui est susceptible de continuer à changer à mesure que l'équipe React essaie de nouvelles choses.
Rendering and performance in React apps
Nous avons maintenant compris que le rendu signifie collecter des informations et qu'il n'est pas nécessaire que cela entraîne des modifications visuelles du DOM à chaque fois. Nous savons également que ce que nous considérons comme «rendu» est un processus en deux étapes impliquant le rendu et la validation. Nous allons maintenant voir comment le rendu (et plus important encore, le re-rendu) est déclenché dans les applications React et comment ne pas connaître les détails peut entraîner de mauvaises performances des applications.
Re-rendu en raison de la modification du composant parent
Si un composant parent dans React change (par exemple, parce que son état ou ses accessoires ont changé), React parcourt l'arborescence entière vers le bas de cet élément parent et restitue tous les composants. Si votre application comporte de nombreux composants imbriqués et de nombreuses interactions, vous subissez sans le savoir un énorme impact sur les performances à chaque fois que vous modifiez le composant parent (en supposant que ce soit juste le composant parent que vous vouliez changer).
Certes, le rendu ne provoquera pas la modification du DOM réel par React car, lors de la réconciliation, il détectera que rien n'a changé pour ces composants. Mais, c'est toujours du temps processeur et de la mémoire gaspillés, et vous seriez surpris de la rapidité avec laquelle cela s'additionne.
Re-rendu en raison du changement de contexte
La fonctionnalité de contexte de React semble être l'outil de gestion d'état préféré de tout le monde (quelque chose pour lequel elle n'a pas été conçue du tout). Tout est si pratique - enveloppez simplement le composant le plus haut dans le fournisseur de contexte, et le reste est simple! La majorité des applications React sont construites de cette manière, mais si vous avez lu cet article jusqu'à présent, vous avez probablement repéré ce qui ne va pas. Oui, chaque fois que l'objet de contexte est mis à jour, il déclenche un re-rendu massif de tous les composants de l'arborescence.
La plupart des applications ne sont pas conscientes des performances, donc personne ne le remarque, mais comme indiqué précédemment, de telles omissions peuvent être très coûteuses dans les applications à volume élevé et à forte interaction.
Improving React rendering performance
Alors, compte tenu de tout cela, que pouvons-nous faire pour améliorer les performances de nos applications? Il s'avère qu'il y a quelques choses que nous pouvons faire, mais notez que nous ne discuterons que dans le contexte des composants fonctionnels. Les composants basés sur les classes sont fortement déconseillés par l'équipe React et sont en voie de disparition.
Utilisez Redux ou des bibliothèques similaires pour la gestion de l'état
Ceux qui aiment le monde rapide et sale de Context ont tendance à détester Redux, mais cette chose est extrêmement populaire pour de bonnes raisons. Et l'une de ces raisons est la performance - le connect()
La fonction dans Redux est magique car elle ne rend (presque toujours) correctement que ces composants si nécessaire. Oui, suivez simplement l'architecture Redux standard et les performances sont gratuites. Ce n'est pas du tout exagéré que si vous adoptez l'architecture Redux, vous évitez tout de suite la plupart des problèmes de performances (et autres).
Utilisez memo()
pour «geler» des composants
Le nom «mémo» vient de Memoization, qui est un nom sophistiqué pour la mise en cache. Et si vous n'avez pas beaucoup rencontré de mise en cache, ça va; voici une description édulcorée: chaque fois que vous avez besoin d'un résultat de calcul / opération, vous regardez à l'endroit où vous avez conservé les résultats précédents; si vous le trouvez, très bien, renvoyez simplement ce résultat; sinon, continuez et effectuez cette opération / calcul.
Avant de plonger directement dans memo()
, voyons d'abord comment un rendu inutile se produit dans React. Nous commençons par un scénario simple: une infime partie de l'interface utilisateur de l'application qui montre à l'utilisateur combien de fois il a aimé le service / produit (si vous avez du mal à accepter le cas d'utilisation, pensez à comment sur Medium vous pouvez "applaudir ”Plusieurs fois pour montrer à quel point vous soutenez / aimez un article).
Il y a aussi un bouton qui leur permet d'augmenter les likes de 1. Et enfin, il y a un autre composant à l'intérieur qui montre aux utilisateurs les détails de leur compte de base. Ne vous inquiétez pas du tout si vous trouvez cela difficile à suivre; Je vais maintenant fournir un code étape par étape pour tout (et il n'y en a pas beaucoup), et à la fin, un lien vers un terrain de jeu où vous pouvez jouer avec l'application de travail et améliorer votre compréhension.
Commençons par aborder le composant sur les informations client. Créons un fichier appelé CustomerInfo.js
qui contient le code suivant:
import React from "react";
export const CustomerInfo = () => {
console.log("CustomerInfo was rendered! :O");
return (
<React.Fragment>
<p>Name: Sam Punia</p>
<p>Email: sampunia@gmail.com</p>
<p>Preferred method: Online</p>
</React.Fragment>
);
};
Rien d'extraordinaire, non?
Juste un texte d'information (qui aurait pu être passé à travers des accessoires) qui ne devrait pas changer lorsque l'utilisateur interagit avec l'application (pour les puristes, oui, bien sûr, cela peut changer, mais le fait est que, par rapport au reste de l'application, c'est pratiquement statique). Mais remarquez le console.log()
déclaration. Ce sera notre indice pour savoir que le composant a été rendu (rappelez-vous, «rendu» signifie que ses informations ont été collectées et calculées / comparées, et non pas qu'elles ont été peintes sur le DOM réel).
Ainsi, lors de nos tests, si nous ne voyons aucun message de ce type dans la console du navigateur, notre composant n'a pas été rendu du tout; si nous le voyons apparaître 10 fois, cela signifie que le composant a été rendu 10 fois; etc.
Et maintenant, voyons comment notre composant principal utilise ce composant d'informations client:
import React, { useState } from "react";
import "./styles.css";
import { CustomerInfo } from "./CustomerInfo";
export default function App() {
const [totalLikes, setTotalLikes] = useState(0);
return (
<div className="App">
<div className="LikesCounter">
<p>You have liked us {totalLikes} times so far.</p>
<button onClick={() => setTotalLikes(totalLikes + 1)}>
Click here to like again!
</button>
</div>
<div className="CustomerInfo">
<CustomerInfo />
</div>
</div>
);
}
Donc, nous voyons que le App
le composant a un état interne géré via le useState()
crochet. Cet état continue de compter le nombre de fois où l'utilisateur a aimé le service / site et est initialement mis à zéro. Rien de difficile en ce qui concerne les applications React, non? Du côté de l'interface utilisateur, les choses ressemblent à ceci:

Le bouton semble trop tentant pour ne pas être brisé, du moins pour moi! Mais avant de faire cela, je vais ouvrir la console de développement de mon navigateur et l'effacer. Après cela, je vais casser le bouton plusieurs fois, et voici ce que je vois:

J'ai appuyé sur le bouton 19 fois, et comme prévu, le nombre total de likes est de 19. La palette de couleurs rendait la lecture très difficile, j'ai donc ajouté une boîte rouge pour mettre en évidence l'essentiel: le <CustomerInfo />
le composant a été rendu 20 fois!
Pourquoi 20?
Une fois lorsque tout a été initialement rendu, puis 19 fois lorsque le bouton a été appuyé. Le bouton change totalLikes
, qui est un morceau d'état à l'intérieur du <App />
composant, et par conséquent, le composant principal effectue un nouveau rendu. Et comme nous l'avons appris dans les sections précédentes de cet article, tous les composants qu'il contient sont également restitués. Ceci est indésirable car le <CustomerInfo />
Le composant n'a pas changé dans le processus et a pourtant contribué au processus de rendu.
Comment pouvons-nous empêcher cela?
Exactement comme le titre de cette section l'indique, en utilisant le memo()
pour créer une copie «préservée» ou mise en cache du <CustomerInfo />
composant. Avec un composant mémorisé, React regarde ses accessoires et les compare aux accessoires précédents, et s'il n'y a pas de changement, React n'extrait pas une nouvelle sortie de «rendu» de ce composant.
Ajoutons cette ligne de code à notre CustomerInfo.js
fichier:
export const MemoizedCustomerInfo = React.memo(CustomerInfo);
Ouais, c'est tout ce que nous devons faire! Il est maintenant temps de l'utiliser dans notre composant principal et de voir si quelque chose change:
import React, { useState } from "react";
import "./styles.css";
import { MemoizedCustomerInfo } from "./CustomerInfo";
export default function App() {
const [totalLikes, setTotalLikes] = useState(0);
return (
<div className="App">
<div className="LikesCounter">
<p>You have liked us {totalLikes} times so far.</p>
<button onClick={() => setTotalLikes(totalLikes + 1)}>
Click here to like again!
</button>
</div>
<div className="CustomerInfo">
<MemoizedCustomerInfo />
</div>
</div>
);
}
Oui, seules deux lignes ont changé, mais je voulais quand même montrer tout le composant. Rien n'a changé au niveau de l'interface utilisateur, donc si je prends la nouvelle version pour un tour et que j'écrase le bouton J'aime plusieurs fois, j'obtiens ceci:

Alors, combien de messages console avons-nous?
Seulement un! Cela signifie qu'en dehors du rendu initial, le composant n'a pas du tout été touché. Imaginez les gains de performances sur une application à très grande échelle! D'accord, d'accord, le lien vers le terrain de jeu de code que j'ai promis est ici. Pour reproduire l'exemple précédent, vous devrez importer et utiliser CustomerInfo
au lieu de MemoizedCustomerInfo
à partir de CustomerInfo.js
.
Cela dit, memo()
n'est pas du sable magique que vous pouvez saupoudrer partout et vous attendre à des résultats magiques. Surutilisation de memo()
peut également introduire des bogues délicats dans votre application et, parfois, simplement provoquer l'échec de certaines mises à jour attendues. Le conseil général sur l'optimisation «prématurée» s'applique également ici. Tout d'abord, développez votre application selon votre intuition; puis, faites un profilage intensif pour voir quelles parties sont lentes et s'il semble que les composants mémorisés sont la bonne solution, introduisez-la seulement.
Conception de composants «intelligents»
Je mets «intelligent» entre guillemets parce que: 1) L'intelligence est hautement subjective et situationnelle; 2) Les actions supposées intelligentes ont souvent des conséquences désagréables. Donc, mon conseil pour cette section est: ne soyez pas trop confiant dans ce que vous faites.
Avec cela à l'écart, une possibilité d'améliorer les performances de rendu est de concevoir et de placer les composants un peu différemment. Par exemple, un composant enfant peut être refactorisé et déplacé vers le haut de la hiérarchie de manière à échapper aux rendus. Aucune règle ne dit: «le composant ChatPhotoView doit toujours être à l'intérieur du composant Chat». Dans des cas particuliers (et ce sont des cas où nous avons des preuves étayées par des données que les performances sont affectées), contourner / enfreindre les règles peut en fait être une excellente idée.
Conclusion
Beaucoup plus peut être fait pour optimiser Applis de réaction en général, mais puisque cet article est sur le rendu, j'ai limité la portée de la discussion. Quoi qu'il en soit, j'espère que vous avez maintenant un meilleur aperçu de ce qui se passe dans React sous le capot, de ce qu'est réellement le rendu et de la façon dont cela peut affecter les performances des applications.
Ensuite, comprenons qu'est-ce que React Hooks?