Accueil / Articles / À l’intérieur de React Fiber : les unités de travail, Render vs Commit, et les voies à priorité.

À l’intérieur de React Fiber : les unités de travail, Render vs Commit, et les voies à priorité.

Découvrez ce qu’est réellement React Fiber, pourquoi l’ancien mécanisme de conciliation bloquait le thread principal, et comment les unités de travail, les deux phases et les voies permettent un rendu concurrentiel.

3495 mots

« Fiber » est l’un de ces termes React qui est mentionné bien plus souvent qu’il n’est expliqué. Les développeurs l’entendent décrit comme un nouveau moteur, un remplaçant du Virtual DOM, ou quelque chose lié aux Hooks, mais aucune de ces descriptions n’est tout à fait exacte. Ce guide construit le concept depuis zéro : d’abord le problème que rencontrait React avant la version 16, puis ce qu’est Fiber, comment le rendu se divise en deux phases, et comment le planification et les priorités s’y inscrivent. À la fin, vous devriez être capable d’expliquer Fiber avec précision, de repérer les idées reçues courantes et de comprendre pourquoi des API comme startTransition en dépendent.

Fiber en une phrase

React Fiber est l’architecture interne de réconciliation incluse avec React 16. Son rôle est de rendre le processus de rendu contrôlable. Plutôt que de traiter une mise à jour comme une tâche unique et indivisible, React la modélise en de nombreuses petites unités qu’il peut traiter une par une et classer selon leur importance.

Cette évolution est la plus facile à comprendre en comparant les deux approches. Avant Fiber, une mise à jour suivait une trajectoire linéaire du début à la fin :

Before Fiber:

Update
  ↓
Render entire tree
  ↓
Commit changes
  ↓
Done

Avec Fiber, une étape intermédiaire apparaît où le travail est divisé en fragments, et ces fragments peuvent être ordonnés, mis en pause ou reprises avant que quoi que ce soit n’atteigne l’écran :

After Fiber:

Update
  ↓
Break work into units
  ↓
Process units
  ↓
Prioritize / pause / resume when appropriate
  ↓
Commit changes

Cette étape supplémentaire est à l’origine de presque toutes les fonctionnalités modernes de rendu dans React. Pour comprendre pourquoi elle a justifié une réécriture, il suffit de regarder ce qui existait auparavant.

Le réconciliateur de pile et son point aveugle

Les versions de React avant 16 utilisaient ce que l’on appelle généralement le Stack Reconciler. Le processus global était déjà familier : un changement d’état entraîne une rendu, ce dernier est comparé au résultat précédent, puis le DOM est mis à jour.

State Change
    ↓
  Render
    ↓
Reconciliation
    ↓
DOM Updates

Le problème était que la réconciliation s’effectuait de manière synchrone. Ce nom provient du fait que le réconciliateur parcourait l’arborescence par des appels de fonction récursifs ordinaires, de sorte que l’avancement du travail se situait directement sur la pile d’appels JavaScript. Une fois que React commençait à traiter une mise à jour, il n’y avait aucun moyen naturel de s’arrêter en cours de route, car cela signifierait dérouler cette pile et perdre son état actuel. Imaginez une application de taille moyenne :

App
│
├── Header
├── Sidebar
├── Dashboard
│   ├── Chart
│   ├── Table
│   └── Statistics
├── Notifications
└── Footer

Si une mise à jour affecte une grande partie de cet arbre, React parcourt les composants concernés en une seule passe continue, du premier au dernier, sans jamais restituer le contrôle :

Start rendering
      ↓
  Component A
      ↓
  Component B
      ↓
  Component C
      ↓
  Component D
      ↓
  Component E
      ↓
     ...
      ↓
   Finish

Le résultat était correct ; le problème résidait dans le temps. React ne pouvait pas interrompre son travail pour le reprendre plus tard, ce qui obligeait tout le reste à attendre.

Pourquoi un rendu long nuit à l’utilisateur

JavaScript ne dispose pas de thread principal exclusif. Le navigateur utilise le même thread pour exécuter les gestionnaires d’événements, calculer la mise en page, dessiner les pixels et générer les frames :

Browser Main Thread
│
├── JavaScript
├── Event Handling
├── Layout
├── Paint
└── Rendering

Lorsqu’un script occupe le thread pendant une longue période, toutes ces tâches s’accumulent en file d’attente derrière lui. Du point de vue de l’utilisateur, la chaîne d’événements se présente ainsi :

Click
 ↓
React starts large update
 ↓
Main thread remains busy
 ↓
Browser can't respond quickly
 ↓
UI feels slow

Un clic est enregistré avec retard, une frappe apparaît après un délai notable, une animation est saccadée. Dans une petite application, le processus de rendu est suffisamment court pour que personne ne s’en aperçoive. Cependant, à mesure que l’application se développe et que les interactions augmentent, un framework a besoin de pouvoir décider quand les tâches de rendu sont exécutées et dans quelle mesure elles le sont en même temps. C’est là que Fiber a été conçu pour combler ce manque.

L’idée principale : travailler en unités planifiables

Le modèle conceptuel derrière Fiber se résume en une seule phrase : le rendu est divisé en unités de travail gérables que React peut planifier et prioriser.

Sous l’ancien modèle, l’instruction adressée au mécanisme de synchronisation n’était en réalité qu’un seul ordre :

"Render this entire tree."

Avec Fiber, React peut plutôt analyser chaque élément individuel et déterminer lequel mérite d’être traité en premier :

"Here is one piece of work."
"Here is another piece."
"Which work should I process first?"

Au lieu d’une appel récursif profond, l’utilisation de parties distinctes permet à React d’arrêter après n’importe quelle partie, de vérifier s’il y a des tâches plus importantes à effectuer, puis de reprendre plus tard.

Qu’est-ce qu’une fibre en réalité ?

fibre est un objet JavaScript ordinaire qui représente une unité de travail dans l’arbre interne de React. En pratique, il y a approximativement une fibre par composant ou élément participant au processus de réconciliation. Prenons cet petit arbre de composants :

App
│
├── Header
├── Sidebar
└── Content

Conceptuellement, React maintient une structure parallèle composée de fibres :

Fiber Tree

App Fiber
   │
   ├── Header Fiber
   ├── Sidebar Fiber
   └── Content Fiber

Les objets Fiber réels contiennent bien plus qu’un simple nom. Ils enregistrent leurs liens vers les nœuds parents, enfants et frères/sœurs, le type et l’identité du composant, les propriétés et l’état en attente, ainsi que les effets qui doivent être exécutés une fois le travail finalisé. Ce sont ces liens explicites qui permettent à React de parcourir l’arborescence par boucle plutôt que par récursion, ce qui rend possible l’arrêt et la reprise du processus. Cependant, pour comprendre l’architecture dans son ensemble, il suffit de se rappeler une formule : une Fiber est une unité du travail de rendu et de conciliation de React.

Fiber n’est pas un remplaçant du Virtual DOM

On affirme souvent que Fiber a « remplacé le Virtual DOM ». Ce n’est pas le cas. Les deux répondent à des questions différentes.

Le Virtual DOM est une description de l’apparence que doit avoir l’interface utilisateur, que React compare à la description précédente pour déterminer ce qui doit changer :

Virtual DOM
    ↓
"What should the UI look like?"

Fiber est le mécanisme que React utilise pour organiser et exécuter les tâches nécessaires pour atteindre cet objectif :

Fiber
    ↓
"How should React organize and process the work required to get there?"

Ils travaillent ensemble, mais l’un représente l’interface utilisateur tandis que l’autre constitue un moyen de traiter les tâches. Si vous souhaitez en savoir plus sur cette comparaison, consultez comment le mécanisme de différenciation du Virtual DOM de React détermine ce qui doit être mis à jour.

L’arbre des fibers lors d’une mise à jour

React conserve son arbre de fibers tout au long de la durée de vie de l’application. Un exemple un peu plus détaillé montre comment un sous-arbre s’insère à l’intérieur de cet arbre :

                 App
                  │
        ┌─────────┼─────────┐
        ↓         ↓         ↓
      Header    Sidebar    Content
                            │
                     ┌──────┴──────┐
                     ↓             ↓
                   Chart          Table

Lorsque les props ou l’état changent, React utilise cette structure pour identifier les branches qui nécessitent des modifications et ignorer le reste.

Rendre le rendu interrompable

La conséquence la plus importante de cette conception est que le rendu peut être mis en pause. Prenons un mises à jour qui se divise en plusieurs étapes :

Large Update
     ↓
   Work 1
     ↓
   Work 2
     ↓
   Work 3
     ↓
   Work 4

Avec le mécanisme de synchronisation classique, les quatre étapes devaient être exécutées l’une après l’autre. Avec Fiber, React peut traiter quelques-unes d’entre elles, céder la parole au navigateur afin qu’il puisse répondre aux entrées ou afficher une frame, avant de reprendre :

Work 1
  ↓
Work 2
  ↓
Pause
  ↓
Browser gets an opportunity to handle other work
  ↓
Resume
  ↓
Work 3
  ↓
Work 4

Cela ne constitue pas en soi une optimisation de vitesse ; le travail total reste identique. Le travail ne monopolise simplement plus la thread, ce qui permet à React de planifier d’autres tâches.

Deux phases : identifier les changements, puis les appliquer

React moderne ne considère pas une mises à jour comme un seul passage du rendu au DOM :

Render → DOM

Au contraire, chaque mises à jour passe par deux phases distinctes :

             React Update
                  │
                  ↓
             Render Phase
                  │
                  ↓
            Commit Phase
                  │
                  ↓
                 DOM

La phase de rendu calcule l’interface utilisateur suivante

Pendant la phase de rendu, React répond à la question de l’aspect que doit avoir l’interface utilisateur en ce moment. Plus concrètement, il :

  • exécute les fonctions des composants et traite les mises à jour en attente
  • construit ou met à jour l’arbre de fibres
  • détermine ce qui diffère de l’arbre actuel
  • collecte la liste des modifications à appliquer

Sous forme de diagramme, l’arbre précédent et les nouvelles mises à jour y sont introduits, et une description des tâches nécessaires en ressort :

Previous Tree
      +
 New Updates
      ↓
Reconciliation
      ↓
   New Work

Puisque rien dans cette phase ne touche au DOM, elle peut être interrompue dans React moderne. React peut calculer une grande partie de l’arbre suivant en arrière-plan sans que personne ne voie d’état intermédiaire. Cela explique également pourquoi React exige que le rendu soit exempt d’effets secondaires : lors du rendu concurrent, un composant peut être rendu plusieurs fois avant que son résultat ne soit enregistré, et en mode strict, la logique de rendu est délibérément appelée deux fois afin de mettre en évidence les codes qui ne fonctionnent pas sous cette hypothèse.

La phase d’enregistrement applique le résultat

Lorsque les modifications sont connues, React les enregistre :

Render Phase
     ↓
Changes determined
     ↓
Commit Phase
     ↓
DOM updated

C’est pendant la phase d’enregistrement que se produisent les véritables mutations : des nœuds DOM sont insérés, mis à jour ou supprimés, des refs sont attachés, et les effets de mise en page s’exécutent. Une façon pratique de séparer ces deux phases :

Render Phase
"Let's figure out what needs to change."

Commit Phase
"Now apply those changes."

Pourquoi l’enregistrement ne peut pas être suspendu

Si l’arrêt temporaire est si utile, pourquoi ne pas l’appliquer partout ? Parce que l’écran doit rester cohérent. Imaginez que React s’arrête à mi-chemin lors de l’écriture dans le DOM :

Update A
 ↓
DOM partially changed
 ↓
Pause
 ↓
Update B

L’utilisateur verrait un mélange d’interfaces anciennes et nouvelles qui n’a jamais existé logiquement. C’est pourquoi React sépare les différentes tâches : la mise en œuvre des modifications peut être interrompue, relancée ou abandonnée, mais le commit applique un résultat final d’un seul coup. Cette séparation est essentielle pour comprendre React moderne.

Planification : toutes les mises à jour ne sont pas égales

Avec le travail divisé en unités, React peut déterminer quelles tâches sont les plus importantes. Certaines mises à jour sont clairement plus urgentes que d’autres. En réponse à cela :

User clicks a button

c’est plus important pour l’utilisateur à ce moment-là que cela :

Rendering a large list somewhere else

De la même manière, cela :

Typing in an input

Il faut que l’action soit instantanée. Une mise à jour importante se produisant ailleurs sur la page ne doit pas faire en sorte que les caractères restent en retard par rapport à l’entrée clavier. Fiber fournit la structure qui permet à React d’évaluer de telles différences et d’organiser le travail en conséquence.

Lanes : comment React attribue une priorité

En interne, les balises React modernes gèrent les mises à jour via des lanes, qui en codent la priorité. Le code de l’application ne traite presque jamais directement ces lanes ; React les utilise pour déterminer quelles mises à jour en attente doivent être traitées lors d’un rendu donné et lesquelles peuvent attendre. Vue simplifiée :

                Updates
                   │
       ┌───────────┼───────────┐
       ↓           ↓           ↓
     Urgent      Normal      Deferred
       │           │           │
       ↓           ↓           ↓
    Process      Process     Process
    sooner       normally    later

Il est utile de séparer trois termes liés lorsqu’ils apparaissent ensemble :

Fiber
 ↓
Represents work

Scheduler
 ↓
Helps coordinate when work should happen

Lanes
 ↓
Represent priority of updates

Fiber décrit le travail à effectuer, le planificateur détermine quand il s’exécute, et les canaux indiquent le degré d’urgence de chaque mise à jour. La mise en œuvre concrète de ces éléments a changé entre les différentes versions de React, il convient donc de considérer cela comme un modèle conceptuel plutôt que comme une description du code source actuel.

Vieille version contre nouvelle version, étape par étape

Au préalable de Fiber, la réconciliation était synchrone et basée sur la pile, et une fois lancée, elle se poursuivait simplement sans interruption :

Update
  ↓
Reconcile
  ↓
Continue
  ↓
Continue
  ↓
Continue
  ↓
Finish

Il y avait peu de possibilités d’interrompre le travail ou de privilégier une mise à jour par rapport à une autre.

L’architecture Fiber intègre des décisions de planification au sein du pipeline :

Update
  ↓
Create / schedule work
  ↓
Process Fiber units
  ↓
Prioritize
  ↓
Pause / resume / restart when appropriate
  ↓
Complete render
  ↓
Commit

En plaçant les deux flux côte à côte, la différence devient évidente :

BEFORE FIBER:

Component Tree
      ↓
Synchronous Reconciliation
      ↓
Finish Everything
      ↓
   Commit


AFTER FIBER:

Component Tree
      ↓
Fiber Tree
      ↓
Units of Work
      ↓
Prioritize / Schedule
      ↓
    Render
      ↓
    Commit

L’objectif n’était pas que React devienne plus rapide, mais plutôt qu’il prenne le contrôle sur la manière et le moment auquel le rendu s’effectue.

Idées reçues courantes

Fiber ne crée pas de threads

Fiber ne divise pas React en plusieurs threads :

React
 ├── Thread 1
 ├── Thread 2
 └── Thread 3

Le JavaScript de React s’exécute toujours sur le thread principal du navigateur. Fiber est une forme de planification coopérative : React cède volontairement le contrôle entre différentes tâches afin que d’autres opérations puissent s’exécuter. Cela diffère fondamentalement des Web Workers, qui exécutent réellement du code sur un thread séparé.

Concurrent ne signifie pas simultané

Fiber est ce qui rend possible le rendu concurrent, mais le terme « concurrent » est facile à mal interpréter. Il ne signifie pas que React rend tout en même temps. Il veut dire que React peut préparer une mise à jour sans bloquer l’application pendant toute la durée d’un rendu long. Une séquence typique :

Low-priority update
       ↓
React starts rendering
       ↓
Higher-priority update arrives
       ↓
React can prioritize the important work
       ↓
Continue / restart lower-priority work

Un rendu à faible priorité peut être mis de côté lorsque quelque chose de plus urgent arrive, pour être ensuite repris ou relancé à zéro par la suite. C’est le mécanisme qui permet de nombreuses interactions plus fluides dans React moderne. Pour comprendre comment cela fonctionne dans un cadre de développement, l’article sur le pré-rendu partiel et le rendu concurrent aborde l’aspect Next.js.

Fiber n’est pas « un composant à la fois »

Il est également tentant d’imaginer Fiber comme un système qui rend exactement un composant, puis le suivant, et ainsi de suite :

Component 1
Component 2
Component 3

C’est trop simpliste. La navigation, le regroupement et le planification des tâches dans React sont bien plus complexes qu’une simple liste linéaire ; ce que Fiber offre, c’est un modèle de travail bien plus détaillé que l’ancienne approche récursive.

Les transitions en pratique

Les transitions sont là où le planification de Fiber devient visible dans le code de l’application. Prenons une boîte de recherche dans laquelle l’utilisateur vient juste de taper :

User types: "rea"

Cette frappe déclenche deux types d’opérations différentes :

1. Update the input immediately
2. Update a huge search result list

Mettre à jour l’entrée est ce que l’utilisateur observe ; reconstruire une longue liste de résultats peut être coûteux. React permet de marquer le deuxième type d’opération comme non urgent en enveloppant la mise à jour d’état dans startTransition :

startTransition(() => {
    setSearchResults(results);
});

Les caractères tapés sont traités comme une mise à jour urgente afin que le champ reste réactif :

User Input
    ↓
Urgent Update
    ↓
Keep UI responsive

La liste de résultats est traitée comme une transition, que React peut afficher avec une priorité inférieure et interrompre si l’utilisateur continue de taper :

Search Results
    ↓
Transition
    ↓
Can be handled with lower priority

Deux remarques pratiques. Premièrement, les transitions ne rendent pas le travail coûteux moins cher ; elles empêchent simplement qu’il ne bloque les mises à jour urgentes, de sorte qu’une liste lente peut encore bénéficier de la mémorisation ou de la virtualisation. Deuxièmement, l’état propre de l’entrée doit rester en dehors des transitions, sinon la saisie elle-même devient différable et le champ semble lent. Tout ce planification n’aurait pas été possible sans la phase de rendu interrompable que Fiber fournit.

Pourquoi React avait besoin d’une nouvelle base

Au préalable de Fiber, React disposait déjà de la plupart des éléments que l’on associe à lui :

  • un Virtual DOM
  • la réconciliation
  • un modèle de composants
  • des mises à jour du DOM efficaces

La refonte visait l’avenir, et non la correction de problèmes existants. Les applications se développaient simultanément sur plusieurs axes :

Larger
   +
More interactive
   +
More data-driven
   +
More complex

Cette croissance a obligé React à se soucier de plus que simplement ce qui a changé. Il devait également répondre à des questions telles que le moment auquel une tâche doit s’exécuter, l’importance d’une mise à jour, la possibilité de mettre en pause une tâche, le fait qu’une autre mise à jour doive être traitée en priorité, ainsi que les moyens d’éviter de bloquer les interactions qui comptent le plus pour les utilisateurs. Fiber est l’architecture qui permet de répondre à ces questions.

Un scénario de tableau de bord

Imaginons un tableau de bord analytique contenant plusieurs zones gourmandes en ressources :

Dashboard
│
├── Navigation
├── Filters
├── Revenue Chart
├── User Chart
├── Large Data Table
└── Notifications

Lorsque l’utilisateur modifie un filtre, une implémentation naïve pourrait réafficher les graphiques et le grand tableau en une seule opération, ce qui rendrait le contrôle de filtrage lui-même lent à répondre. Avec l’architecture Fiber, la même interaction est gérée via des tâches priorisées :

User changes filter
        ↓
Update begins
        ↓
React performs reconciliation
        ↓
Work is represented through Fiber
        ↓
Updates can be prioritized
        ↓
Rendering completes
        ↓
Commit changes

Les graphiques et les tableaux doivent encore être recalculés. Ce qui s’améliore, c’est la réactivité, puisque React gère cette tâche.

Fiber par rapport aux concepts voisins

Fiber et le Virtual DOM

Le Virtual DOM est une représentation de l’interface utilisateur que React utilise pour déterminer ce qui doit changer :

UI State
   ↓
Virtual Representation

Fiber est l’architecture interne et la structure de données à travers lesquelles React représente et traite le rendu :

Component / Element
       ↓
     Fiber
       ↓
Reconciliation + Scheduling

Ensemble, ils forment le système de rendu de React :

Virtual DOM
      +
Fiber Architecture
      ↓
React Rendering System

Relatés, mais non interchangeables.

Fiber et les Web Workers

Ces derniers traitent des problèmes complètement distincts. Fiber concerne :

Rendering
Reconciliation
Scheduling
Prioritization

Un Web Worker, en revanche, a pour but :

Running JavaScript
outside the main UI execution context

Sa structure ressemble à ceci, avec les calculs intensifs déplacés hors du thread qui gère l’UI :

Main Thread
     │
     ├── UI
     ├── React
     │
     ↓
Web Worker
     │
     ↓
Heavy computation

Les fibres ne transfèrent jamais le rendu de React vers un travailleur. Si vous avez des tâches gourmandes en CPU qui ne concernent pas le rendu, comme l’analyse de données ou des calculs numériques, un travailleur reste le bon outil ; l’article sur les travailleurs dédiés, partagés et de service explique ces options en détail.

Que cela signifie pour votre code

Dans le travail quotidien, vous ne touchez jamais directement aux fibres. Vous écrivez des composants :

function App() {
    return <Dashboard />;
}

et vous déclenchez des mises à jour :

setState(newValue);

React transforme tout cela en tâches exécutées par des fibres en arrière-plan. Votre code se trouve au sommet d’une chaîne de traitement dont les couches inférieures nécessitent rarement réflexion de votre part :

Your React Code
      ↓
React APIs
      ↓
Fiber Architecture
      ↓
Reconciliation
      ↓
Scheduling / Prioritization
      ↓
    Commit
      ↓
     DOM

On ne crée jamais manuellement des nœuds Fiber. Ce dont on a besoin, c’est d’un modèle mental : garder la logique de rendu pure, placer les effets secondaires dans des fonctions d’effet ou des gestionnaires, et utiliser des transitions pour les mises à jour coûteuses mais non urgentes.

Le résumé le plus concis possible

L’ancien mécanisme de réconciliation suivait une règle simple :

"Start rendering → keep going → finish."

Fiber suit une autre approche :

"Break rendering into work → decide how to schedule it
→ complete the render → commit the result."

Cette différence résume en somme tout le changement architectural.

En conclusion

Le mécanisme de réconciliation basé sur la pile a bien servi React pendant des années. Fiber répond aux applications qui ont dépassé ses capacités. L’évolution va d’un contrôle limité :

Old React
   ↓
Synchronous Stack Reconciler
   ↓
Limited control over rendering work

à un modèle où le travail est explicite, planifiable et priorisé :

React Fiber
   ↓
Fiber Tree + Units of Work
   ↓
More flexible reconciliation
   ↓
Scheduling + Prioritization
   ↓
Concurrent Rendering Capabilities

Lorsqu’on demande ce qu’est React Fiber, qualifier cela de « nouvel moteur de rendu » est une description insuffisante. Une réponse plus précise serait que Fiber représente l’architecture interne de réconciliation de React, qui modélise le rendu en unités de travail permettant à React d’planifier, de prioriser, d’interruption et de reprendre ces tâches au moment opportun.

Points clés

  • Fiber a été introduit dans React 16 et a remplacé le réconciliateur en pile synchrone et récursif.
  • Une fibre est un objet représentant une unité de travail de rendu, reliée au sein d’un arbre que React peut parcourir et mettre en pause.
  • La phase de rendu calcule les modifications et peut être interrompue ; la phase d’application les applique en une seule étape ininterrompue.
  • Les canaux et le planificateur utilisent la structure de Fiber pour donner la priorité aux mises à jour urgentes par rapport à celles différées.
  • Fiber n’est ni le Virtual DOM ni le multithreading ; il s’agit d’un planification coopérative sur le thread principal.
  • Le rendu et les transitions concurrents sont basés sur cette fondation, mais ils gèrent des tâches coûteuses plutôt que de les éliminer.
  • Lectures complémentaires