Un composant React refait son rendu quand son propre état change, quand son parent refait son rendu, ou quand un contexte qu'il lit change. Faire un rendu signifie que React appelle à nouveau votre fonction composant pour obtenir un JSX neuf ; il ne met ensuite à jour que les parties de la page qui ont réellement changé.
Ouvrez la console sous l'aperçu et cliquez sur le bouton. Les trois composants écrivent, y compris Title, qui ne prend aucune prop. Il a fait son rendu parce que son parent a fait le sien.
Ce qui déclenche un rendu
React fait le rendu d'un composant exactement pour ces raisons :
- Le premier rendu. L'application démarre, ou le composant apparaît dans l'arbre pour la première fois.
- Son état a changé. Vous avez appelé un setter de
useStateou un dispatch deuseReduceravec une nouvelle valeur. - Son parent a fait son rendu. Par défaut, quand un composant fait son rendu, chaque composant qu'il renvoie fait aussi le sien, jusqu'en bas de l'arbre.
- Un contexte qu'il lit a changé. Un composant qui appelle
useContext(SomeContext)refait son rendu quand le provider le plus proche passe une nouvellevalue(voir useContext).
Une idée répandue veut qu'un composant refasse son rendu « parce que ses props ont changé ». C'est prendre la cause à l'envers. Les props sont des arguments que le parent passe pendant son rendu, donc de nouvelles props ne peuvent arriver que quand le parent fait son rendu. Et le rendu du parent suffit à lui seul : Title ci-dessus n'a pas de props et refait quand même son rendu à chaque fois.
Donner à l'état la valeur qu'il a déjà (comparée avec Object.is) ne lance pas le rendu des enfants. React peut quand même appeler ce composant une fois avant de remarquer que rien n'a changé, mais il jette le résultat.
Rendu et commit
Chaque mise à jour passe par deux phases :
- Rendu. React appelle vos composants. Ils renvoient du JSX, c'est-à-dire de simples objets qui décrivent ce que l'écran doit afficher. Rien ne change encore sur la page, c'est pourquoi le rendu doit être pur : pas d'écriture dans le DOM, pas de requêtes, pas de modification de variables hors du composant.
- Commit. React compare le nouveau résultat au précédent et applique les différences au DOM : il insère, retire ou met à jour seulement les nœuds qui ont changé. Ensuite le navigateur affiche la page, et après cela React exécute vos effets.
L'exemple ci-dessous fait un rendu à chaque clic, mais React garde le même élément <input>. Un effet s'exécute après chaque commit et le vérifie.
Tapez quelque chose dans le champ et cliquez plusieurs fois. Le texte tapé reste, car React n'a jamais remplacé le champ : le seul changement du DOM à chaque clic est le nombre dans le <p>. La console montre aussi l'ordre : render d'abord, et l'effet après le commit.
Le DOM virtuel, simplement
« DOM virtuel » est le nom courant des objets que vos composants renvoient. <p>Items: {count}</p> se compile en un appel qui crée un objet comme { type: 'p', props: { children: ['Items: ', 1] } }. Après un rendu, React parcourt le nouvel arbre de ces objets à côté du précédent, et là où le type et la position correspondent, il garde le nœud DOM existant et ne met à jour que les attributs et le texte modifiés. Cette comparaison s'appelle la réconciliation.
Le terme est approximatif pour deux raisons. React ne garde pas une seconde copie du DOM pour comparer deux DOM : il compare des objets éléments à son propre arbre interne de composants (l'arbre de fibers). Et le même processus fait fonctionner des cibles qui n'ont pas de DOM du tout, comme React Native. La documentation de React évite le plus souvent l'expression et parle plutôt de rendu et de commit. Ce qui compte en pratique, c'est la conséquence : le rendu coûte peu par rapport au travail sur le DOM, car la plupart des rendus se terminent par une petite mise à jour du DOM, voire aucune.
Deux règles découlent du fonctionnement de la comparaison. Un type d'élément différent au même endroit (une <div> remplacée par une <section>, ou ComponentA par ComponentB) détruit l'ancien sous-arbre et son état. Et dans les listes, key indique à React quel élément est lequel, pour qu'il puisse déplacer des nœuds au lieu de les reconstruire.
Éviter les rendus inutiles
La plupart des rendus en trop ne coûtent rien de perceptible. Quand l'un d'eux pose problème, par exemple une grande liste qui refait son rendu à chaque frappe ailleurs sur la page, essayez ces solutions dans l'ordre.
Descendre l'état
Si une seule petite partie de l'écran utilise un élément d'état, gardez cet état dans un composant qui n'enveloppe que cette partie. Cette version refait le rendu de ProductList à chaque frappe :
export default function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ProductList />
</>
);
}
Déplacez le champ et son état dans leur propre composant, et la liste ne se trouve plus dans le composant qui fait son rendu :
Tapez quelques lettres : seul SearchBox écrit.
Passer children à la place
Parfois, l'état doit vivre dans une enveloppe autour de la partie coûteuse, comme un panneau qui active une mise en surbrillance. Faites accepter children à l'enveloppe. Le parent crée les éléments enfants, et comme le parent ne refait pas son rendu quand l'état de l'enveloppe change, ces éléments sont les mêmes objets qu'avant, et React les saute.
Cliquez sur la bascule et seul Highlighter écrit. Déplacez maintenant <Article /> dans le JSX de Highlighter à la place de {children}, et chaque clic affiche aussi render Article.
Mémoïser
Quand aucune réorganisation ne convient, enveloppez l'enfant dans memo. React compare alors ses props aux précédentes et saute le rendu quand elles sont toutes identiques. Les objets et les fonctions créés pendant le rendu sont nouveaux à chaque fois, donc memo s'accompagne généralement de useMemo ou useCallback pour ces props. La page sur React.memo le montre avec des exemples exécutables.
import { memo } from 'react';
const ProductList = memo(function ProductList({ category }) {
// skipped while category stays the same
});
React Compiler peut ajouter ce type de mémoïsation pour vous au moment du build, mais réorganiser l'état reste la première chose à essayer : cela supprime le travail au lieu de le mettre en cache.
Les rendus ne posent généralement pas de problème
Un rendu est un appel de fonction qui renvoie des objets. React en exécute des milliers rapidement, et un rendu qui produit le même résultat ne modifie pas le DOM. N'ajoutez pas memo partout « par sécurité » : chaque comparaison a son propre coût et rend le code plus difficile à lire. Mesurez d'abord. Le Profiler des React DevTools montre quels composants ont fait leur rendu, pourquoi, et combien de temps chacun a pris.
Questions fréquentes
Qu'est-ce qui provoque un nouveau rendu d'un composant React ?
Trois choses : son propre état change, son parent fait son rendu, ou un contexte qu'il lit avec useContext reçoit une nouvelle valeur. Les props ne sont pas un déclencheur à part : de nouvelles props n'arrivent que parce que le parent a fait son rendu.
Un enfant refait-il son rendu quand le parent refait le sien ?
Oui, par défaut chaque composant à l'intérieur d'un parent qui fait son rendu fait aussi le sien, même si ses props n'ont pas changé. Envelopper l'enfant dans memo permet à React de le sauter quand ses props sont les mêmes que la fois précédente.
Qu'est-ce que le DOM virtuel dans React ?
C'est un nom approximatif pour l'arbre d'objets JavaScript simples (des éléments React) que vos composants renvoient. React compare le nouvel arbre au précédent et ne modifie que les nœuds du vrai DOM qui diffèrent.
Les nouveaux rendus nuisent-ils aux performances ?
En général, non. Un rendu est un appel de fonction qui produit des objets, et React ne touche au DOM que là où le résultat a changé. Optimisez quand un rendu est lent de façon mesurable, par exemple avec le Profiler des React DevTools.
Quelle est la différence entre rendu et commit ?
Le rendu, c'est React qui appelle vos composants pour savoir à quoi l'écran doit ressembler. Le commit, c'est React qui applique les différences au DOM. Un rendu qui produit le même résultat ne commit aucun changement du DOM.