Comment le navigateur affiche l’interface et quel rôle joue React
Découvrez comment le Critical Rendering Path, la réconciliation, Fiber et le Scheduler collaborent pour transformer les mises à jour de React en pixels à l’écran.
Tout d’abord, oubliez React : comment un navigateur affiche-t-il réellement une page ?
Mettez React de côté un instant. Même un document HTML simple avec quelques règles CSS doit suivre une séquence précise d’étapes avant que le moindre pixel ne soit affiché. Chaque navigateur respecte cette séquence, quelles que soient les outils utilisés pour créer la page :
HTML → DOM tree
CSS → CSSOM tree
DOM + CSSOM → Render Tree
Render Tree → Layout → Paint → Composite → Screen
Chaque étape a une fonction spécifique, et les noms seuls ne le rendent pas évident :
- Arbre DOM — le navigateur analyse votre HTML et le convertit en un arbre de nœuds. Il s’agit purement d’une structure indiquant quels éléments se trouvent à l’intérieur desquels.
- Arbre CSSOM — la même logique s’applique aux styles. Chaque règle CSS que vous écrivez est transformée en un arbre que le navigateur peut consulter.
display: none reste bien dans le DOM mais est exclu de l’arbre de rendu).Ces étapes réunies forment ce qu’on appelle le chemin de rendu critique, ou CRP.
Cette séquence s’exécute de la même manière que vous utilisiez React, Vue, jQuery pur ou aucune bibliothèque du tout. Le rendu des pixels relève de la responsabilité du navigateur, et non d’un framework UI.
Alors, quelle est la place de React dans tout cela ?
C’est là que réside l’aspect qui nécessite un instant pour être compris : React ne remplace pas le chemin de rendu critique — il agit en amont de celui-ci.
Sans React, mettre à jour l’interface utilisateur signifie localiser manuellement le nœud DOM approprié et le modifier soi-même :
const counterEl = document.getElementById('counter');
counterEl.textContent = newCount;
Cela est gérable pour un simple compteur. Mais imaginez une interface de tableau de bord où quarante valeurs distinctes peuvent changer indépendamment — il faudrait alors suivre et mettre à jour chacune d’elles manuellement. C’est précisément ce problème que React est conçu pour résoudre.
Avec React, la même mise à jour se présente comme ceci :
function Counter({ count }) {
return <p>{count}</p>;
}
Vous décrivez à quoi devrait ressembler l’interface en fonction des données actuelles, sans jamais toucher directement au DOM. Il faut donc que quelque chose d’autre effectue ce travail. C’est là la véritable fonction de React, et cela a lieu avant même que le parcours de rendu critique du navigateur ne commence :
State changes → React figures out what changed → applies a small patch to the real DOM
↓
browser does its normal thing: Layout → Paint → Composite → Screen
Toute la valeur ajoutée de React réside dans le fait qu’il rend cette première étape — déterminer ce qui a changé — aussi rapide et précise que possible, afin que le navigateur n’ait à retraiter que la petite partie de la page qui en a vraiment besoin, plutôt que tout le contenu.
Impératif versus déclaratif : le changement de mentalité
Cet contraste explique pourquoi React a été conçu de cette manière.
Le code impératif décrit chaque étape individuellement :
list.innerHTML = '';
for (const item of items) {
const li = document.createElement('li');
li.textContent = item;
list.appendChild(li);
}
C’est à vous de décider : effacer ceci, créer cet élément, l’attacher ici.
Un code déclaratif, en revanche, décrit le résultat que vous souhaitez voir :
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
Plutôt que d’indiquer au navigateur de « créer un li et de l’insérer », on précise « étant donné cet array, voici à quoi doit ressembler l’interface utilisateur résultante ». Quelque chose d’autre doit alors transformer cette description en opérations concrètes sur le DOM — et comprendre ce « quelque chose » est la étape suivante.
Que signifie vraiment « réconciliation »
Cette notion semble plus compliquée qu’elle ne l’est en réalité lorsqu’on y regarde directement.
React conserve une représentation mentale de votre interface utilisateur, appelée Virtual DOM. Chaque fois qu’un changement a lieu, React crée une nouvelle version de cet arbre et la compare à l’ancienne pour déterminer ce qui a changé. Cette étape de comparaison est ce que l’on appelle reconciliation.
Vérifier toutes les différences possibles entre deux arbres serait extrêmement coûteux en termes de calcul, c’est pourquoi React utilise une astuce avec deux règles qui permettent de maintenir des performances élevées dans un usage réel :
- Le type d’élément a changé (par exemple, un `
` transformé en ``) — React ne regarde même pas les enfants ; il élimine complètement l’ancien nœud et en crée un nouveau.
- Le type d’élément reste identique (`
devenant
`) — React conserve le nœud DOM réel existant et ne modifie que les parties qui diffèrent.
Un piège qui touche presque tout le monde concerne les listes. Par défaut, React compare les éléments d’une liste en fonction de leur indice — en mettant l’élément 0 en correspondance avec l’élément 0, l’élément 1 avec l’élément 1, et ainsi de suite. Si vous insérez une nouvelle entrée au début d’une liste sans clé, React suppose que tous les éléments situés en dessous ont également changé.
C’est pourquoi vous devez toujours attribuer une clé stable (key) aux éléments de liste :
{items.map(item => <li key={item.id}>{item.name}</li>)}
Lorsque les éléments disposent d’une clé, React peut reconnaître que « cet élément en particulier a simplement changé de position » au lieu de supposer que toute la liste a été reconstruite.
Après toutes ces comparaisons, React obtient un ensemble restreint d’instructions ciblées — comme « mettre à jour ce nœud de texte » ou « insérer un nœud ici » — et ce sont ces opérations qui sont réellement appliquées au DOM réel.
N’est-ce pas la même chose que fait Fiber ?
C’est une question légitime, qui mérite d’être examinée en détail.
La réconciliation est l’idée fondamentale. Fiber n’est rien d’autre que le mécanisme qui la met en œuvre.
Au préalable à React 16, ce mécanisme s’appelait Stack Reconciler. Il parcourait toute la structure de manière récursive et synchrone, ce qui signifie qu’une fois lancé, il devait être exécuté jusqu’au bout avant de s’arrêter. Pour une mise à jour importante, cela pouvait occuper la thread principale suffisamment longtemps pour que l’application paraisse lente — les images disparaissaient, et la saisie devenait peu réactive.
Fiber, introduit dans React 16, a remplacé ce mécanisme. Le concept de réconciliation n’a pas changé, mais désormais le travail est divisé en petits lots qui peuvent être mis en pause, éliminés ou repris plus tard. Si quelque chose de plus urgent apparaît — comme les saisies de l’utilisateur — React peut interrompre le travail à moindre priorité qu’il était en train d’effectuer, gérer la mise à jour urgente, puis reprendre là où il s’était arrêté.
Il n’est donc pas exact de dire que Fiber a remplacé la réconciliation. Il est plus juste de dire que le moteur ancien qui effectuait cette réconciliation a été remplacé par un autre plus performant.
Alors, quel est le rôle du Scheduler ?
Fiber rend possible de mettre en pause et de reprendre le travail, mais il faut autre chose pour décider quand mettre en pause et quelle tâche mérite la priorité. C’est là le rôle du Scheduler.
Ses responsabilités incluent :
- Mise à jour du classement en fonction de l’urgence — par exemple, traiter une frappe dans un champ d’entrée comme urgente tandis qu’un renouvellement lointain d’une liste en arrière-plan ne l’est pas.
- Premier remplissage des espaces entre les frames du navigateur pour progresser progressivement sur des tâches à moindre priorité, puis retour en arrière avant que le prochain frame ne doive être rendu.
- Mise en œuvre des fonctionnalités de React 18 telles que
startTransition, où déclarer une mise à jour comme non urgente indique en fait au planificateur qu’il peut la déplacer vers le bas de la liste des priorités.
Reconciliation → the algorithm (what changed?)
Fiber → the engine that makes that algorithm interruptible
Scheduler → the traffic controller deciding when to pause/resume Fiber's work
Phase de rendu versus phase d’application
Il existe une autre distinction à comprendre : Fiber divise son travail en deux phases qui suivent des règles très différentes.
La phase de rendu est celle où a lieu la comparaison des différences. React appelle les fonctions de votre composant, assemble le nouvel arbre et le compare à l’ancien. Rien de tout cela ne touche encore la page réelle, c’est pourquoi cette phase peut être suspendue, ignorée ou relancée en toute sécurité.
La phase d’application est celle où React écrit finalement dans le DOM réel et applique les modifications calculées. Cette phase ne peut pas être interrompue — elle s’exécute du début à la fin en une seule passe continue, car une mise à jour de l’interface partiellement appliquée laisserait la page dans un état visuel défectueux. Immédiatement après mise à jour du DOM, mais avant que le navigateur ne dessine l’écran, useLayoutEffect s’exécute de manière synchrone. En revanche, useEffect s’exécute un peu plus tard, une fois que le navigateur a terminé de dessiner.
Avez-vous vraiment besoin de React ?
Honnêtement, pas toujours. De nombreux sites web de production fonctionnent uniquement avec HTML, CSS et JavaScript pur, et ils fonctionnent très bien.
React commence à justifier ses surcoûts lorsque vos exigences deviennent plus complexes :
- Configurer manuellement les mises à jour du DOM est gérable pour de petits projets, mais cela devient impossible lorsque l’on doit gérer des dizaines d’éléments UI interdépendants.
- Une grande partie des bugs d’interface dans la vie réelle provient du désalignement entre l’état et l’interface affichée. L’approche de React — traiter l’UI comme une fonction de l’état et laisser le framework gérer les différences — élimine en grande partie ce risque par conception.
- La capacité à créer des composants réutilisables, soutenus par un écosystème d’outils de routage, d’outils de développement et de conventions communes, devient précieuse lorsque plus d’un développeur travaille sur la même base de code.
Cependant, pour une page d’accueil simple ou un site largement statique, du JavaScript pur est le meilleur choix. Intégrer Fiber, le Scheduler ainsi que tout le pipeline de réconciliation signifierait assumer des coûts supplémentaires pour un problème que l’on n’a en réalité jamais eu.
React n’est pas intrinsèquement supérieur à JavaScript. C’est un ensemble d’outils conçu pour résoudre un problème spécifique : maintenir l’interface utilisateur synchronisée avec des états qui changent constamment, à grande échelle, au sein d’une équipe. En deçà de cette échelle, du HTML, du CSS et du JS purs suffisent amplement à gérer la tâche.
Lectures complémentaires
- Résoudre le surcharge des props dans React grâce à la composition et aux slots — Découvrez pourquoi les props de React complexes entraînent des difficultés de maintenance, et comment l’inversion de contrôle, la composition et les slots permettent de créer des composants véritablement réutilisables.