Menu

La prop children en React : composants enveloppes et slots

children est la prop qui contient tout ce que vous placez entre les balises ouvrante et fermante d'un composant. Apprenez à construire des composants enveloppes et de mise en page, à passer plusieurs emplacements avec des props nommées, à utiliser children comme fonction, et quand l'API Children n'est pas le bon outil.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Dans React, children est la prop qui contient tout ce que vous écrivez entre les balises ouvrante et fermante d'un composant. Un composant qui affiche {children} quelque part dans son résultat devient une enveloppe : il fournit le cadre, et l'appelant fournit le contenu.

Les deux cartes partagent la même bordure, le même padding et le même style de titre, mais chacune contient un contenu totalement différent. Ajoutez une troisième <Card title="Help"> avec un lien à l'intérieur et elle reçoit le même cadre gratuitement.

Comment children arrive au composant

Le JSX transforme le contenu imbriqué en prop. Ces deux lignes produisent le même élément :

<Card title="Profile"><p>Ada</p></Card>

<Card title="Profile" children={<p>Ada</p>} />

children est donc une prop ordinaire, transmise d'une façon particulière. Vous la déstructurez comme n'importe quelle autre (function Card({ children })) ou la lisez via props.children. La page sur les props traite des props en général.

Ce qui arrive dans children dépend de ce que vous avez écrit entre les balises. Un seul enfant arrive tel quel. Plusieurs enfants arrivent sous forme de tableau. Du texte arrive sous forme de chaîne, et l'absence de contenu arrive comme undefined.

La console affiche object, array of 2, string et undefined. Vous avez rarement à vous en soucier : afficher {children} gère tous ces cas, et null, undefined, true et false n'affichent rien. La forme ne compte que si vous essayez d'inspecter ou de modifier children, ce contre quoi la dernière section met en garde.

Composants de mise en page

L'usage le plus courant de children est un composant de mise en page : une structure de page, une mise en page avec barre latérale, une colonne centrée. Le composant gère la structure et les espacements, et chaque page qui l'utilise passe son propre contenu.

function PageLayout({ children }) {
    return (
        <div className="page">
            <Header />
            <main className="page-content">{children}</main>
            <Footer />
        </div>
    );
}

function AboutPage() {
    return (
        <PageLayout>
            <h1>About us</h1>
            <p>We teach people to code.</p>
        </PageLayout>
    );
}

PageLayout ne sait pas et n'a pas besoin de savoir ce que contient une page « à propos ». C'est tout l'intérêt de cette séparation : modifiez l'en-tête une fois et chaque page enveloppée dans la mise en page en profite.

Plusieurs emplacements avec des props nommées

children est un seul emplacement. Quand un composant a besoin de contenu à plusieurs endroits, passez les morceaux supplémentaires sous forme de props nommées. N'importe quelle prop peut contenir du JSX, donc une modale peut prendre un title, un footer et son corps dans children.

La structure de la modale contrôle les bordures, le padding et l'ordre des trois zones, et l'appelant remplit chacune d'elles. D'autres frameworks appellent cela des slots ; dans React, ce sont simplement des props. Le <>...</> autour des deux boutons est un fragment, qui les regroupe sans ajouter d'élément. Une vraie boîte de dialogue serait aussi rendue dans document.body avec un portail pour s'afficher au-dessus de la page.

Déplacez le <p> dans la prop title et le texte du corps apparaît dans l'en-tête : le composant décide où va chaque prop, l'appelant décide seulement de ce qu'elle contient.

children comme fonction

Parfois, l'enveloppe possède un état et c'est l'appelant qui doit décider comment le dessiner. Au lieu d'un élément, l'appelant passe une fonction dans children, et l'enveloppe l'appelle avec les valeurs. Ce pattern s'appelle une render prop.

Un Toggle dessine un bouton et l'autre une case à cocher, à partir de la même logique. Les render props étaient le principal moyen de partager une logique avec état avant les hooks. Aujourd'hui, un hook personnalisé comme useToggle() se lit généralement mieux, mais vous verrez encore des render props dans des bibliothèques de listes, de formulaires et d'animation.

L'API Children et cloneElement

React exporte un objet Children avec des utilitaires pour parcourir children : Children.map, Children.forEach, Children.count, Children.toArray et Children.only. Avec cloneElement, qui copie un élément avec de nouvelles props, ils permettent à un parent d'inspecter et de modifier ce qu'il a reçu. L'équipe React les classe parmi les API legacy : ils fonctionnent encore, mais le nouveau code devrait les éviter.

La raison est qu'ils ne voient que les éléments écrits directement entre les balises. Ils ne voient pas ce que ces éléments affichent.

La liste affiche quatre éléments, mais la console indique Children.count = 3, car TwoMore est un seul enfant, peu importe combien d'éléments il affiche. cloneElement transmet aussi la prop style à TwoMore, qui l'ignore, donc les troisième et quatrième éléments ne sont pas rayés. Quiconque refactorise quelques éléments <li> en un composant casse le parent sans y toucher.

La solution est d'arrêter de fouiller dans children et de passer des données à la place :

function NumberedList({ items }) {
    return (
        <ol>
            {items.map((item, i) => (
                <li key={item.id} style={{ color: i % 2 ? 'gray' : 'black' }}>
                    {item.label}
                </li>
            ))}
        </ol>
    );
}

Maintenant, la liste gère l'affichage de chaque ligne, et rien ne dépend de la façon dont l'appelant a écrit son JSX. Quand un parent doit partager des valeurs avec des enfants profondément imbriqués (un onglet sélectionné, un thème), utilisez le contexte plutôt que de cloner des props sur eux.

Typer children en TypeScript

En TypeScript, typez children comme React.ReactNode. Il couvre tout ce que React peut afficher : éléments, chaînes, nombres, tableaux de ces valeurs, null, undefined et booléens.

import type { ReactNode } from 'react';

type CardProps = {
    title: string;
    children: ReactNode;
};

function Card({ title, children }: CardProps) {
    return (
        <section>
            <h3>{title}</h3>
            {children}
        </section>
    );
}

Pour une render prop, typez-la comme la fonction qu'elle est : children: (on: boolean, toggle: () => void) => ReactNode. Rendez children facultative (children?: ReactNode) quand le composant a aussi un sens vide.

Erreurs courantes

Oublier d'afficher children. Si un composant accepte des children mais ne place jamais {children} dans son résultat, le contenu disparaît sans bruit. Rien ne vous avertit.

Appeler children comme une fonction alors qu'elle n'en est pas une. children() ne fonctionne que si l'appelant a passé une fonction. Si un composant attend une render prop, indiquez-le dans son nom ou sa documentation, ou acceptez une prop nommée comme render pour que l'intention soit claire.

Modifier children. Les éléments sont en lecture seule. React les gèle en développement, donc affecter une valeur à children.props lève une erreur. Construisez plutôt un nouveau résultat.

Questions fréquentes

Qu'est-ce que props.children dans React ?

C'est le contenu écrit entre les balises ouvrante et fermante d'un composant. Dans <Card><p>Hi</p></Card>, la fonction Card reçoit le <p> dans props.children et décide où l'afficher.

children est-elle une prop spéciale ?

Seulement par la façon dont on la passe. Le JSX place le contenu imbriqué dans une prop appelée children, mais à l'intérieur du composant c'est une prop ordinaire. Vous pouvez aussi la passer explicitement, comme dans <Card children={<p>Hi</p>} />, et cela fonctionne pareil.

Comment passer plusieurs blocs de contenu à un composant ?

Utilisez des props nommées pour les blocs supplémentaires. Une prop peut contenir du JSX comme children, donc <Modal title={<h2>Delete?</h2>} footer={<button>OK</button>}>Body</Modal> donne trois emplacements au composant.

Faut-il utiliser Children.map et cloneElement ?

Évitez-les dans le nouveau code. Ils ne voient que les éléments écrits directement entre les balises, pas ce que ces éléments affichent, et cassent donc dès que quelqu'un enveloppe un enfant dans un autre composant. Passez plutôt un tableau de données en prop, ou utilisez le contexte.

Quel est le type de children en TypeScript ?

Utilisez React.ReactNode. Il couvre tout ce que React peut afficher : éléments, chaînes, nombres, tableaux, null, undefined et booléens.

Illustration des langages de programmation de Coddy

Apprendre à coder avec Coddy

COMMENCER