Accueil / Articles / Les fragments React et StrictMode : un marquage plus léger et une détection précoce des erreurs

Les fragments React et StrictMode : un marquage plus léger et une détection précoce des erreurs

Apprenez comment les fragments React regroupent des éléments sans enveloppes DOM supplémentaires, et comment la double invocation réservée au développement de StrictMode met en évidence les rendus et effets impurs.

976 mots

Deux fonctionnalités de React ne dessinent jamais rien à l’écran, mais elles influencent presque tous les arbres de composants : les fragments et StrictMode. Les fragments permettent de maintenir le DOM exempt d’éléments conteneurs qui n’existent que pour satisfaire JSX, tandis que StrictMode met délibérément à l’épreuve vos composants en phase de développement afin que les logiques impropres soient détectées avant les utilisateurs.

Pourquoi JSX a besoin d’une racine unique

JSX compile chaque balise en une appel de fonction, ce qui signifie que deux balises sœurs retournées ensemble forment deux valeurs là où une seule est attendue. Cela entraîne un échec de compilation :

return (
  <h1>Hello</h1>
  <p>Welcome</p>
);

La solution traditionnelle consistait à envelopper les balises sœurs dans un div:

return (
  <div>
    <h1>Hello</h1>
    <p>Welcome</p>
  </div>
);

Ce nœud supplémentaire a des inconvénients : il alourdit le DOM, perturbe les layouts flex ou grid qui exigent des enfants directs, complique les sélecteurs, et peut générer du HTML invalide ou moins accessible.

Grouper sans conteneur

Un Fragment regroupe des enfants pour React sans créer d’élément DOM. Sa syntaxe concise consiste en une paire de balises vides :

return (
  <>
    <h1>Hello</h1>
    <p>Welcome</p>
  </>
);

La forme explicite, React.Fragment, fait la même chose et est nécessaire lorsque l’on doit transmettre une propriété :

return (
  <React.Fragment>
    <h1>Hello</h1>
    <p>Welcome</p>
  </React.Fragment>
);

Le HTML généré ne contient que les éléments h1 et p. L’amélioration des performances est minime ; l’avantage réel réside dans une balisage correct.

Rétablissement des éléments frères

Un composant qui génère plusieurs éléments similaires peut laisser le gestionnement du layout à son parent :

function Card() {
  return (
    <>
      <h2>Title</h2>
      <p>Description</p>
    </>
  );
}

Fragment avec clé dans les listes

Lorsqu’on itère sur des données où chaque élément produit plus d’un élément, React a toujours besoin d’une clé stable par élément. La syntaxe concise <> ne permet pas d’accepter de propriétés, il faut donc utiliser React.Fragment avec une clé :

items.map(item => (
  <React.Fragment key={item.id}>
    <h2>{item.title}</h2>
    <p>{item.description}</p>
  </React.Fragment>
));

Celles de tableau et lignes

Les tables HTML disposent d’un modèle de contenu strict : un tr ne peut contenir que des td ou des th. Un div d’encapsulation est invalide dans ce cas. Un Fragment permet à un composant de contribuer plusieurs cellules à une ligne appartenant à son parent :

function Row() {
  return (
    <>
      <td>A</td>
      <td>B</td>
    </>
  );
}

Quelles vérifications effectue StrictMode

StrictMode ne rend rien, n’ajoute aucun élément et n’a aucun effet sur les versions de production. En développement, il active des vérifications supplémentaires, notamment :

  • avertissements pour les méthodes de cycle de vie des classes obsolètes considérées comme dangereuses, telles que componentWillMount
  • avertissements pour les API dépréciées comme les références de chaîne et findDOMNode
  • appel répété des corps de composants, des initialisateurs et des fonctions de mise à jour afin de détecter un rendu impur
  • exécution des effets via un cycle de configuration, de nettoyage et de réconfiguration supplémentaire lors du montage pour identifier un manque de nettoyage

La liste exacte a changé au fil des versions de React, il convient donc de consulter la documentation correspondante à votre version.

Rendu double intentionnel

Imaginons un composant qui enregistre des informations au fur et à mesure de son rendu :

function App() {
  console.log("Rendered!");
  return <h1>Hello</h1>;
}

Sous StrictMode en mode développement, le tableau de bord affiche ce message deux fois :

Rendered!
Rendered!

C’est intentionnel. Une fonction de rendu doit être pure : avec les mêmes props et état, elle doit toujours retourner le même résultat sans modifier quoi que ce soit en dehors d’elle-même. L’appel de cette fonction deux fois permet de détecter les violations, comme la modification d’une variable partagée, et de constater des résultats clairement erronés. La pureté est importante car le rendu concurrentiel peut commencer, s’arrêter, ignorer ou répéter des rendus, et le code qui suppose un seul rendu par mise à jour échoue dans de telles conditions.

L’activer

Enveloppez l’arborescence que vous souhaitez vérifier, généralement toute l’application, à la racine :

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Effets sous StrictMode : l’array de dépendances n’est pas la solution

Voici un effet sans array de dépendances. Il s’exécute après chaque rendu, ce qui fait que chaque mise à jour déclenche une nouvelle requête :

useEffect(() => {
  console.log("Fetching...");
  fetch("/api");
});

Ajouter un array vide limite l’exécution de l’effet au moment du montage :

useEffect(() => {
  console.log("Fetching...");
  fetch("/api");
}, []); // stable dependency

Ce changement est correct, mais il n’empêche pas les doubles requêtes en mode développement. Depuis React 18, StrictMode monte le composant, exécute sa fonction de nettoyage, puis le monte à nouveau, ce qui fait que l’effet avec [] s’exécute toujours deux fois. Cette double exécution est un signe, pas une erreur. La véritable solution consiste en une fonction de nettoyage qui rend la deuxième exécution inoffensive, généralement en annulant la première requête à l’aide d’un AbortController ou en ignorant son résultat grâce à un indicateur. Un tel effet est également sécurisé face aux vrais remontages en production.

Côte à côte

  • Objectif : Les fragments regroupent des éléments ; StrictMode met en évidence les erreurs.
  • Résultat DOM : aucun des deux n’ajoute d’élément.
  • Impact en production : aucun pour l’un comme pour l’autre.
  • Comportement au runtime : les fragments s’affichent normalement ; StrictMode déclenche deux fois les rendus et effets uniquement en phase de développement.
  • Avantages : un markup plus propre et valide par rapport à du code prévisible et exempt d’effets secondaires.

Une configuration minimale à essayer

export default function App() {
  return (
    <>
      <h1>Hello World</h1>
      <p>Rendered using Fragments</p>
    </>
  );
}

StrictMode, de sorte que toute logique impure ajoutée par la suite est immédiatement détectée en phase de développement :

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

Points clés

  • Utilisez un Fragment chaque fois qu’un élément conteneur n’existerait que pour satisfaire JSX, et employez React.Fragment avec une key dans les listes.
  • Gardez StrictMode activé pendant le développement ; les logs et effets doubles sont des diagnostics intentionnels.
  • Résolvez les problèmes d’effets exécutés deux fois en assurant un nettoyage adéquat, et non en manipulant les dépendances.
  • Lectures complémentaires