Avec la vie, la mort, le destin et les impôts, le comportement de React en matière de rendu est l'une des plus grandes vérités et l'un des plus grands mystères de la vie.
Plongeons-y !
Comme tout le monde, j'ai commencé mon parcours de développeur front-end avec jQuery. La manipulation pure du DOM basée sur JS était un cauchemar à l'époque, c'est pourquoi tout le monde le faisait. Puis, petit à petit, les frameworks basés sur JavaScript sont devenus si importants que je ne pouvais plus les ignorer.
Le premier que j'ai appris est Vue. J'ai eu beaucoup de mal parce que les composants, l'état et tout le reste étaient un modèle mental totalement nouveau, et j'ai eu beaucoup de mal à tout intégrer. Mais j'ai fini par y arriver, et je me suis félicité. Félicitations, mon pote, me suis-je dit, vous avez réussi à gravir la pente raide ; maintenant, le reste des frameworks, si jamais vous devez les apprendre, sera très facile.
Un jour, lorsque j'ai commencé à apprendre React, j'ai réalisé à quel point j'avais tort. Facebook n'a pas rendu les choses plus faciles en ajoutant les Hooks et en disant à tout le monde : " Hey, utilisez ceci à partir de maintenant. Mais ne réécrivez pas les classes ; les classes sont très bien. En fait, pas tellement, mais ça va. Mais les Hooks sont tout, et ils sont l'avenir.
Vous avez compris ? Génial !".
Finalement, j'ai aussi franchi cette montagne. Mais ensuite, j'ai été frappé par quelque chose d'aussi important et difficile que React lui-même : le rendu.

Si vous avez déjà rencontré le rendu et ses mystères dans React, vous savez de quoi je parle. Et si ce n'est pas le cas, 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 y gagnerez (contrairement à moi, qui suis un idiot surexcité et qui apprend volontiers n'importe quoi juste pour le plaisir 😭😭😭😭). Si votre vie de développeur React avance bien sans vous soucier de ce qu'est ce rendu, pourquoi s'en préoccuper ? Bonne question, alors répondons-y d'abord, puis nous verrons ce qu'est réellement le rendu.
Pourquoi est-il important de comprendre le comportement du rendu dans React ?
Nous commençons tous à apprendre React en écrivant des composants (de nos jours, fonctionnels) qui renvoient quelque chose appelé JSX. Nous comprenons également que ce JSX est en quelque sorte converti en éléments DOM HTML réels qui apparaissent sur la page. Les pages se mettent à jour au fur et à mesure que l'état se met à jour, les routes 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, il arrive que nous trouvions certaines parties de notre application (ou l'application entière) remarquablement lentes. Et le pire, c'est que nous n'avons pas la moindre idée de la raison de cette lenteur ! Nous avons tout fait correctement, nous ne voyons aucune erreur ou avertissement, nous avons suivi toutes les bonnes pratiques de conception des composants, les normes de codage, etc. et aucune lenteur du réseau ou calcul coûteux de la logique commerciale ne se passe en coulisses. 🤔
Parfois, le problème est totalement différent : il n'y a pas de problème de performance, mais l'application se comporte bizarrement. Par exemple, elle fait trois appels d'API au backend d'authentification, mais un seul à tous les autres. Ou encore, certaines pages sont redessinées deux fois, la transition visible entre les deux rendus de la même page créant une interface utilisateur déroutante.

Le pire, c'est qu'il n'y a pas d'aide extérieure disponible dans des cas comme celui-ci. Si vous allez sur votre forum de développement préféré et que vous posez cette question, on vous répondra : "Je ne peux pas le dire sans regarder votre application. Pouvez-vous joindre un exemple fonctionnel minimum ici ?" 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 parce qu'il n'interagit pas avec l'ensemble du système de la même manière que dans l'application réelle.
Vous vous êtes fait avoir ? Oui, si vous voulez mon avis. 🤭🤭
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 ; une 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 très loin sans avoir à en connaître tous les recoins.
Qu'est-ce que le rendu dans React ?
C'est une excellente question, mon ami. Nous n'avons pas tendance à la poser lorsque nous apprenons React (je le sais parce que je ne l'ai pas fait) parce que le mot " render " nous berce peut-être d'un faux sentiment de familiarité. Bien que le sens du dictionnaire soit complètement différent (et ce n'est pas important dans cette discussion), nous, programmeurs, avons déjà une idée de ce qu'il devrait signifier. En travaillant avec des écrans, des API 3D, des cartes graphiques et en lisant les spécifications des produits, nous avons appris à penser à quelque chose comme "peindre une image" lorsque nous lisons le mot "rendre". Dans la programmation d'un moteur de jeu, il y a un Renderer, dont le seul travail est de - précisément - peindre le monde tel qu'il a été transmis par la Scène.
Nous pensons donc que lorsque React "rend" quelque chose, il rassemble tous les composants et repeint le DOM de la page web. Mais dans le monde de React (et oui, même dans la documentation officielle), ce n'est pas de cela qu'il s'agit. Alors, serrons nos ceintures et plongeons dans les rouages de React.

Vous devez avoir entendu 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 changements nécessaires (c'est pourquoi vous ne pouvez pas simplement jeter jQuery et React ensemble - React a besoin de prendre le contrôle total du DOM). Maintenant, ce DOM virtuel n'est pas composé d'éléments HTML comme le DOM réel, 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éé cette application React très simple dans ce but. L'ensemble du code se résume à un seul fichier contenant quelques lignes :
import React from "react" ;
import "./styles.css" ;
export default function App() {
const element = (
<div classname="App">
<h1>Bonjour !</h1>
<h2>Regardons les éléments React</h2>
</div>
) ;
console.log(element) ;
return element ;
}
Vous remarquez ce que nous faisons ici ?
Oui, nous enregistrons simplement ce à quoi ressemble un élément JSX. Nous avons écrit des centaines de fois ces expressions et composants JSX, mais nous prêtons rarement attention à ce qui se passe. Si vous ouvrez la console de développement de votre navigateur et que vous exécutez cette application, vous verrez un objet
qui se développe comme suit :

Cela peut sembler intimidant, mais prenez note de quelques détails intéressants :
- Il s'agit d'un objet JavaScript ordinaire et non d'un nœud DOM.
- Remarquez que la propriété
props
indique qu'il a unclassName
deApp
(qui est la classe CSS définie dans le code) et que cet élément a deux enfants (cela correspond également, les éléments enfants étant les balises<h1>
et<h2>
). - La propriété
_source
nous indique où commence le code source du corps de l'élément. Comme vous pouvez le voir, le fichierApp.js
est désigné comme source et la ligne 6 est mentionnée. Si vous regardez à nouveau le code, vous constaterez que la ligne 6 se trouve juste après la balise JSX d'ouverture, ce qui est logique. Les parenthèses JSX contiennent l 'élément React ; elles n'en font pas partie, car elles servent à se transformer en un appelReact.createElement()
plus tard. - La propriété
__proto__
nous indique que cet objet dérive toutes ses propriétés de l'objet
JavaScript racine, ce qui renforce encore l'idée que ce sont des objets JavaScript ordinaires que nous examinons ici.
Nous comprenons maintenant que le DOM virtuel ne ressemble en rien au DOM réel, mais qu'il s'agit d'un arbre d'objets React (JavaScript) représentant l'interface utilisateur à un moment donné.

Épuisés ?
Croyez-moi, je le suis aussi 🙂 Tourner ces idées encore et encore dans ma tête pour essayer de les présenter de la meilleure façon possible, puis trouver les mots pour les faire ressortir et les réorganiser... ce n'est pas facile. 😫
Mais nous nous laissons distraire !
Ayant survécu jusqu'ici, nous sommes maintenant en mesure de répondre à la question que nous cherchions : qu'est-ce que le rendu dans React ?
Eh bien, le rendu est le processus du moteur React qui parcourt le DOM virtuel et collecte l'état actuel, les accessoires, la structure, les changements souhaités dans l'interface utilisateur, etc. React met alors à jour le DOM virtuel en utilisant certains calculs et compare également le nouveau résultat avec le DOM réel de la page. Ce calcul et cette comparaison sont ce que l'équipe React appelle officiellement la "réconciliation", et si vous êtes intéressé par leurs idées et leurs algorithmes pertinents, vous pouvez consulter la la documentation officielle.
Il est temps de s'engager !
Une fois la partie rendue terminée, React démarre une phase appelée "commit", durant laquelle il applique les changements nécessaires au DOM. Ces changements sont appliqués de manière synchrone (l'un après l'autre, bien qu'un nouveau mode qui fonctionne de manière concurrente soit attendu prochainement), et le DOM est mis à jour. Nous ne nous préoccupons pas de savoir exactement quand et comment React applique ces changements, car il s'agit de quelque chose qui se trouve totalement sous le capot et qui est susceptible de continuer à changer au fur et à mesure que l'équipe React expérimente de nouvelles choses.
Rendu et performances dans les applications React
Nous avons compris maintenant que le rendu signifie collecter des informations, et qu'il n'a pas besoin de se traduire par des changements visuels du DOM à chaque fois. Nous savons également que ce que nous considérons comme du "rendering" est un processus en deux étapes impliquant le rendering et le commit. Nous allons maintenant voir comment le rendu (et plus important encore, le re-rendu) est déclenché dans les applications React et comment le fait de ne pas connaître les détails peut entraîner des performances médiocres pour les applications.
Rendu dû à un changement dans le composant parent
Si un composant parent dans React change (par exemple, parce que son état ou ses accessoires ont changé), React parcourt l'ensemble de l'arbre jusqu'à cet élément parent et effectue un nouveau rendu de tous les composants. Si votre application comporte de nombreux composants imbriqués et un grand nombre d'interactions, vous risquez, sans le savoir, de nuire considérablement aux performances à chaque fois que vous modifiez le composant parent (en supposant qu'il s'agisse uniquement du composant parent que vous souhaitez modifier).
Il est vrai que le rendu n'entraînera pas React à modifier le DOM réel car, pendant la réconciliation, il détectera que rien n'a changé pour ces composants. Mais c'est toujours du temps CPU et de la mémoire gaspillés, et vous seriez surpris de voir à quelle vitesse cela s'accumule.
Rendu dû à un changement de contexte
La fonctionnalité Contexte de React semble être l'outil de gestion d'état préféré de tout le monde (alors qu'il n'a pas été conçu pour cela). C'est tellement pratique - il suffit d'envelopper le composant le plus haut dans le fournisseur de contexte, et le reste n'est qu'une simple question ! La majorité des applications React sont construites de cette manière, mais si vous avez lu cet article jusqu'à présent, vous avez probablement remarqué ce qui ne va pas. Oui, chaque fois que l'objet contextuel est mis à jour, il déclenche un re-rendement massif de tous les composants de l'arbre.
La plupart des applications n'ont aucune conscience des performances, donc personne ne s'en aperçoit, mais comme nous l'avons déjà dit, de tels oublis peuvent être très coûteux dans les applications à fort volume et à forte interaction.
Améliorer les performances de rendu de React
Dans ces conditions, que pouvons-nous faire pour améliorer les performances de nos applications ? Il s'avère qu'il y a plusieurs choses que nous pouvons faire, mais notez que nous n'en parlerons que dans le contexte des composants fonctionnels. Les composants basés sur des 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 - la fonction connect()
de Redux est magique car elle rend (presque toujours) correctement seulement les composants nécessaires. Oui, il suffit de suivre l'architecture standard de Redux, et les performances sont gratuites. Il n'est pas du tout exagéré de dire que si vous adoptez l'architecture Redux, vous éviterez immédiatement la plupart des problèmes de performance (et autres).
Utilisez memo()
pour "geler" les composants
Le nom "memo" vient de Memoization, qui est un nom fantaisiste pour la mise en cache. Et si vous n'avez pas beaucoup entendu parler de la mise en cache, ce n'est pas grave ; voici une description édulcorée : chaque fois que vous avez besoin d'un résultat de calcul ou d'opération, vous regardez à l'endroit où vous avez conservé les résultats précédents ; si vous le trouvez, tant mieux, renvoyez simplement ce résultat ; sinon, allez de l'avant et effectuez l'opération ou le calcul.
Avant de plonger directement dans memo()
, voyons d'abord comment le rendu inutile se produit dans React. Nous commençons par un scénario simple : une petite 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 à la façon dont, sur Medium, vous pouvez "applaudir" plusieurs fois pour montrer à quel point vous soutenez/appréciez un article).
Il y a également un bouton qui leur permet d'augmenter le nombre de likes de 1. Et enfin, il y a un autre composant à l'intérieur qui montre aux utilisateurs les détails de base de leur compte. Ne vous inquiétez pas si vous avez du mal à suivre ; je vais maintenant fournir du 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 fonctionnelle et améliorer votre compréhension.
Commençons par le composant concernant les informations sur les clients. Créons un fichier appelé CustomerInfo.js
qui contient le code suivant :
import React from "react" ;
export const CustomerInfo = () => {
console.log("CustomerInfo a été rendu ! :O") ;
return (
<React.Fragment>
<p>Nom : Sam Punia</p>
<p>Courriel : sampunia@gmail.com</p>
<p>Méthode préférée : En ligne</p>
</React.Fragment>
) ;
} ;
Rien d'extraordinaire, n'est-ce pas ?
Juste un texte informatif (qui aurait pu être passé par des props) qui n'est pas censé changer au fur et à mesure que l'utilisateur interagit avec l'application (pour les puristes, oui, bien sûr, il peut changer, mais le fait est que, comparé au reste de l'application, il est pratiquement statique). Mais remarquez la déclaration console.log()
. 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'il a été peint sur le DOM actuel).
Ainsi, au cours 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 ; et ainsi de suite.
Voyons maintenant comment notre composant principal utilise ce composant d'informations sur les clients :
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>Vous nous avons aimé {totalLikes} fois jusqu'à présent.</p>
<button onclick="{()" > setTotalLikes(totalLikes 1)}>
Cliquez ici pour aimer à nouveau !
</button>
</div>
<div classname="CustomerInfo">
<customerinfo />
</div>
</div>
) ;
}
Nous voyons donc que le composant App
possède un état interne géré par le crochet useState()
. Cet état compte le nombre de fois que l'utilisateur a aimé le service/site, et est initialement fixé à zéro. Rien de bien difficile pour les applications React, n'est-ce pas ? Du côté de l'interface utilisateur, les choses se présentent comme suit :

Le bouton est trop tentant pour ne pas être écrasé, du moins pour moi ! Mais avant de le faire, je vais ouvrir la console de développement de mon navigateur et l'effacer. Ensuite, je vais frapper le bouton plusieurs fois, et voici ce que je vois :

J'ai appuyé 19 fois sur le bouton, et comme prévu, le nombre total de likes est de 19. Le schéma de couleurs rendait la lecture difficile, j'ai donc ajouté un cadre rouge pour mettre en évidence l'élément principal : le composant <CustomerInfo />
a été rendu 20 fois !
Pourquoi 20 fois ?
Une fois lorsque tout a été rendu initialement, puis 19 fois lorsque le bouton a été actionné. Le bouton modifie totalLikes
, qui est un élément d'état à l'intérieur du composant <App />
, et en conséquence, le composant principal est rendu à nouveau. Et comme nous l'avons appris dans les sections précédentes de ce billet, tous les composants qui se trouvent à l'intérieur sont également redessinés. Ceci est indésirable parce que le composant <CustomerInfo />
n'a pas changé dans le processus et a pourtant contribué au processus de rendu.
Comment éviter cela ?
Exactement comme le titre de cette section l'indique, en utilisant la fonction memo()
pour créer une copie "préservée" ou mise en cache du composant <CustomerInfo />
. Avec un composant mémorisé, React regarde ses props et les compare aux props précédents, et s'il n'y a pas de changement, React n'extrait pas une nouvelle sortie "render" de ce composant.
Ajoutons cette ligne de code à notre fichier CustomerInfo.js
:
export const MemoizedCustomerInfo = React.memo(CustomerInfo) ;
Oui, c'est tout ce que nous avons à faire ! Il est maintenant temps d'utiliser ceci 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>Vous nous avons aimé {totalLikes} fois jusqu'à présent.</p>
<button onclick="{()" > setTotalLikes(totalLikes 1)}>
Cliquez ici pour aimer à nouveau !
</button>
</div>
<div classname="CustomerInfo">
<memoizedcustomerinfo />
</div>
</div>
) ;
}
Oui, seules deux lignes ont changé, mais je voulais de toute façon montrer l'ensemble du composant. Rien n'a changé au niveau de l'interface utilisateur, donc si je prends la nouvelle version pour un tour et que j'appuie plusieurs fois sur le bouton "like", j'obtiens ceci :

Alors, combien de messages de console avons-nous ?
Un seul ! Cela signifie qu'à part le rendu initial, le composant n'a pas été touché du tout. Imaginez les gains de performance sur une application à grande échelle ! D'accord, d'accord, le lien vers la cour de récréation 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 un sable magique que vous pouvez saupoudrer partout et attendre des résultats magiques. L'utilisation excessive 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 ici aussi. Tout d'abord, construisez votre application selon votre intuition ; ensuite, faites un profilage intensif pour voir quelles parties sont lentes et s'il apparaît que les composants mémoïsés sont la bonne solution, alors seulement introduisez-les.
conception "intelligente" des composants
Je mets le terme "intelligent" entre guillemets pour les raisons suivantes 1) l'intelligence est hautement subjective et situationnelle ; 2) les actions supposées intelligentes ont souvent des conséquences désagréables. Mon conseil pour cette section est donc le suivant : ne soyez pas trop confiant dans ce que vous faites.
Ceci étant dit, 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 remanié et déplacé vers le haut de la hiérarchie afin d'éviter les re-renders. Aucune règle ne dit que "le composant ChatPhotoView doit toujours se trouver à l'intérieur du composant Chat". Dans certains cas particuliers (et il s'agit de cas où nous avons des données prouvant que les performances sont affectées), il peut être judicieux de contourner ou de briser les règles.
Conclusion
Il est possible de faire beaucoup plus pour optimiser les applications React en général, mais comme cet article porte sur le rendu, j'ai limité la portée de la discussion. Quoi qu'il en soit, j'espère que vous avez maintenant une meilleure idée de ce qui se passe sous le capot de React, de ce qu'est réellement le rendu et de la manière dont il peut affecter les performances de l'application.
Ensuite, comprenez ce que sont les Hooks de React
-
J'écris sur, autour et pour l'écosystème des développeurs. Recommandations, tutoriels, discussions techniques - quoi que je publie, je fais de mon mieux pour dissiper la confusion et le flou, et fournir des réponses concrètes basées sur l'expérience personnelle... en savoir plus