Débutant25 min de lecture

Composition de composants

Maîtrise la prop children, apprends les patterns de composition plutôt que l'héritage, et construis des composants flexibles et réutilisables en utilisant les patterns de conteneur et de spécialisation.

La prop children

En HTML, les éléments peuvent contenir d'autres éléments : <div><p>Bonjour</p></div>. React fonctionne de la même manière. Quand tu places du JSX entre les balises ouvrantes et fermantes d'un composant, React passe ce JSX comme une prop spéciale appelée children.

jsx
function Wrapper({ children }) {
  return (
    <div style={{ border: '2px solid blue', padding: '16px' }}>
      {children}
    </div>
  );
}

// Utilisation :
<Wrapper>
  <h1>Bonjour !</h1>
  <p>Ce contenu est passé comme children.</p>
</Wrapper>

La prop children peut être n'importe quoi : du texte, des éléments JSX, des composants, ou même un tableau d'éléments. React affiche tout ce que tu passes comme children.

jsx
// Du texte comme children
<Wrapper>Juste une chaîne de caractères</Wrapper>

// Plusieurs éléments comme children
<Wrapper>
  <Header />
  <Sidebar />
  <MainContent />
</Wrapper>

// Des composants imbriqués comme children
<Wrapper>
  <Wrapper>
    <p>Imbriqué !</p>
  </Wrapper>
</Wrapper>

La prop children est la base de la composition de composants dans React. Elle te permet de créer des composants conteneurs génériques qui n'ont pas besoin de connaître leur contenu à l'avance. Le composant définit la structure et le style, et l'appelant décide de ce qui va à l'intérieur.

Tu peux accéder à children par déstructuration ({ children }) ou par props.children :

jsx
// Les deux sont équivalents :
function Box({ children }) {
  return <div className="box">{children}</div>;
}

function Box(props) {
  return <div className="box">{props.children}</div>;
}

Composition vs. Héritage

En programmation orientée objet, l'héritage est une façon courante de partager des comportements entre classes. Mais dans React, la composition est toujours préférée à l'héritage.

L'équipe React chez Meta (avec des milliers de composants en production) a déclaré qu'elle n'a trouvé aucun cas d'usage où elle recommanderait d'utiliser l'héritage plutôt que la composition.

Pourquoi la composition l'emporte :

  1. Flexibilité. Un composant qui utilise children peut envelopper n'importe quel contenu sans modification. Un composant hérité est enfermé dans la structure de son parent.

  2. Explicité. Les props rendent le flux de données visible. L'héritage cache le comportement dans la hiérarchie de classes.

  3. Simplicité. La composition consiste simplement à passer des props et afficher des children. L'héritage nécessite de comprendre toute la chaîne de classes.

Approche par héritage (non recommandée) :

jsx
// Imagine si les composants utilisaient l'héritage :
class SuccessDialog extends Dialog {
  render() {
    return super.render({ color: 'green', title: 'Succès !' });
  }
}
// Difficile à comprendre, difficile à personnaliser

Approche par composition (recommandée) :

jsx
function Dialog({ title, color, children }) {
  return (
    <div style={{ border: `2px solid ${color}`, padding: '16px' }}>
      <h2>{title}</h2>
      {children}
    </div>
  );
}

function SuccessDialog({ children }) {
  return (
    <Dialog title="Succès !" color="green">
      {children}
    </Dialog>
  );
}

// Facile à comprendre, facile à personnaliser
<SuccessDialog>
  <p>Tes modifications ont été enregistrées.</p>
</SuccessDialog>

La composition te permet de construire des interfaces complexes à partir de pièces simples et réutilisables — comme des blocs LEGO. Chaque composant gère sa propre responsabilité, et ils s'assemblent grâce aux props et children.

Composants spécialisés

Un pattern de composition courant consiste à créer des versions spécialisées d'un composant générique. Le composant spécialisé enveloppe le composant générique et fournit des props par défaut spécifiques.

jsx
// Composant générique
function Button({ variant, size, children, onClick }) {
  const styles = {
    padding: size === 'large' ? '12px 24px' : '8px 16px',
    backgroundColor: variant === 'danger' ? 'red' : 'blue',
    color: 'white',
    border: 'none',
    borderRadius: '4px',
    cursor: 'pointer',
  };

  return (
    <button style={styles} onClick={onClick}>
      {children}
    </button>
  );
}

// Composants spécialisés
function DangerButton({ children, onClick }) {
  return (
    <Button variant="danger" size="large" onClick={onClick}>
      {children}
    </Button>
  );
}

function PrimaryButton({ children, onClick }) {
  return (
    <Button variant="primary" size="large" onClick={onClick}>
      {children}
    </Button>
  );
}

Ce pattern est utile pour :

  • Les bibliothèques UI : Créer un composant de base avec de nombreuses options puis proposer des versions pré-configurées.
  • Les design systems : Garantir un style cohérent en enveloppant des composants génériques avec des valeurs par défaut spécifiques.
  • Les composants métier : Créer des composants spécifiques à ton domaine à partir de composants UI génériques.
jsx
// Plus d'exemples de spécialisation :
function ErrorAlert({ children }) {
  return <Alert type="error" icon="!">{children}</Alert>;
}

function PageLayout({ children }) {
  return <Layout sidebar={<Nav />} header={<Header />}>{children}</Layout>;
}

function AdminPage({ children }) {
  return <PageLayout><RequireAuth role="admin">{children}</RequireAuth></PageLayout>;
}

Chaque niveau ajoute un comportement spécifique tout en déléguant au composant générique sous-jacent.

Pattern de conteneur et slots nommés

Le pattern de conteneur se produit quand un composant ne connaît pas ses children à l'avance. Il agit comme un conteneur et affiche tous les children qui lui sont passés.

jsx
function Card({ title, children }) {
  return (
    <div className="card">
      <h3 className="card-title">{title}</h3>
      <div className="card-body">{children}</div>
    </div>
  );
}

<Card title="Profil utilisateur">
  <img src="avatar.png" alt="Avatar" />
  <p>Jean Dupont</p>
  <p>jean@exemple.com</p>
</Card>

Parfois tu as besoin de plusieurs "slots" — différentes zones d'un composant qui acceptent différents contenus. Bien que React n'ait pas de slots nommés comme certains autres frameworks, tu peux obtenir le même résultat en utilisant des props normales :

jsx
function Layout({ header, sidebar, children }) {
  return (
    <div className="layout">
      <header className="layout-header">{header}</header>
      <aside className="layout-sidebar">{sidebar}</aside>
      <main className="layout-content">{children}</main>
    </div>
  );
}

// Utilisation :
<Layout
  header={<NavBar />}
  sidebar={<SideMenu items={menuItems} />}
>
  <ArticleList articles={articles} />
</Layout>

N'importe quelle prop peut accepter des éléments JSX — pas seulement children. Cela te donne la flexibilité de créer des composants avec plusieurs points d'injection :

jsx
function Modal({ title, footer, children }) {
  return (
    <div className="modal-overlay">
      <div className="modal">
        <div className="modal-header">{title}</div>
        <div className="modal-body">{children}</div>
        <div className="modal-footer">{footer}</div>
      </div>
    </div>
  );
}

<Modal
  title={<h2>Confirmer la suppression</h2>}
  footer={<><button>Annuler</button><button>Supprimer</button></>}
>
  <p>Es-tu sûr de vouloir supprimer cet élément ?</p>
</Modal>

Composants de mise en page

Une utilisation particulièrement puissante de la composition concerne les composants de mise en page. Ces composants définissent la structure visuelle d'une page ou d'une section et te laissent remplir le contenu.

jsx
function SplitPane({ left, right }) {
  return (
    <div style={{ display: 'flex', gap: '16px' }}>
      <div style={{ flex: 1 }}>{left}</div>
      <div style={{ flex: 1 }}>{right}</div>
    </div>
  );
}

<SplitPane
  left={<ContactList contacts={contacts} />}
  right={<ChatWindow messages={messages} />}
/>

Les composants de mise en page sont réutilisables dans toute ton application. Voici quelques patterns courants :

jsx
// Un conteneur centré
function CenterContent({ maxWidth, children }) {
  return (
    <div style={{
      maxWidth: maxWidth || '800px',
      margin: '0 auto',
      padding: '0 16px',
    }}>
      {children}
    </div>
  );
}

// Une mise en page empilée (espacement vertical)
function Stack({ gap, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: gap || '8px' }}>
      {children}
    </div>
  );
}

// Utilisation :
<CenterContent maxWidth="600px">
  <Stack gap="16px">
    <h1>Bienvenue</h1>
    <p>Ceci est centré et empilé.</p>
    <Button>Commencer</Button>
  </Stack>
</CenterContent>

Bonnes pratiques pour la composition :

  1. Garde tes composants focalisés. Chaque composant doit faire une chose et la faire bien.
  2. Préfère la composition à la configuration. Au lieu d'un <Card type="user" showAvatar showBio /> avec de nombreux drapeaux, compose des pièces plus petites : <Card><Avatar /><Bio /></Card>.
  3. Utilise children pour le contenu principal et des props nommées pour les slots secondaires.
  4. N'abstrait pas trop. Crée seulement des composants réutilisables quand tu les réutilises vraiment. L'abstraction prématurée ajoute de la complexité sans bénéfice.

Composition en action

html
<div id="root"></div>
<script src="https://unpkg.com/react@19/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@19/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
  function Card({ title, children }) {
    return (
      <div style={{
        border: '1px solid #ccc',
        borderRadius: '8px',
        padding: '16px',
        marginBottom: '12px',
      }}>
        <h3 style={{ marginTop: 0 }}>{title}</h3>
        <div>{children}</div>
      </div>
    );
  }

  function Badge({ color, children }) {
    return (
      <span style={{
        backgroundColor: color,
        color: 'white',
        padding: '2px 8px',
        borderRadius: '12px',
        fontSize: '12px',
      }}>
        {children}
      </span>
    );
  }

  function App() {
    return (
      <div style={{ maxWidth: '400px', margin: '20px auto' }}>
        <Card title="Fondamentaux de React">
          <p>Apprends les concepts fondamentaux de React.</p>
          <Badge color="green">Débutant</Badge>
        </Card>
        <Card title="Patterns avancés">
          <p>Composition, render props, et plus encore.</p>
          <Badge color="orange">Intermédiaire</Badge>
        </Card>
      </div>
    );
  }

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>

Qu'est-ce que la prop 'children' dans React ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.