Table des matières
Introduction
La projection de contenu, c'est le fait de devoir déplacer du contenu depuis un composant parent vers un composant enfant.
La plupart du temps, il n'y a qu'un seul contenu à déplacer, ce qui fait que les choses sont simple.
Mais parfois, j'ai besoin de "remplir" plusieurs "emplacements" de mon composant, comme par exemple avec une Card
:
<div class="card">
<div class="header">
<!-- Title -->
</div>
<div class="content">
<!-- Content -->
</div>
<div class="footer">
<!-- Actions -->
</div>
</div>
Les 3 emplacements sont ici: Title, Content et Footer.
Dans Angular, il existe quelque chose qui répond exactement à mon besoin: ng-content
et l'attribut select
:
<div class="card">
<div class="header">
<ng-content select=".header"></ng-content>
</div>
<div class="content">
<ng-content select=".content"></ng-content>
</div>
<div class="footer">
<ng-content select=".footer"></ng-content>
</div>
</div>
Ce composant peut s'utiliser de la manière suivante:
<my-card>
<div class="header">
<h3>title</h3>
</div>
<div class="content">
<p>Lorem ipsum</p>
</div>
<div class="actions">
<button>Click me</button>
</div>
</my-card>
Mais pour React, il n'y a qu'un seul children
et il est impossible pour moi de "ranger" les differents enfants dans des emplacements spécifiques.
Les propriétés
Plutôt que de passer naturelement par la propriété children
, je vais créer une propriété pour chaque emplacements.
export const MyCard = ({ header, content, footer }) => (
<div className="card">
<div className="header">{header}</div>
<div className="content">{content}</div>
<div className="footer">{footer}</div>
</div>
);
Et comme il est possible de mettre un template dans une propriété, voici comment ce composant pourra etre utilisé:
<MyCard
header={<h3>title</h3>}
content={<p>Lorem ipsum</p>}
footer={<button>Click me</button>}
/>
Mais je trouve que cela casse la structure du HTML et que le code pourrait vite devenir difficile à maintenir.
Filtrer les children
Je me rend compte que passer par children
reste la meilleur option pour la lisibilité, il faut donc améliorer le
rendu de ce dernier pour "dispatcher" les enfants aux bons endroits.
L'api React me permet de boucler sur les composants passés dans les children
et de créer des filtres.
https://react.dev/reference/react/Children#children-foreach
Si je créer une fonction filterChildren
qui retourne uniquement les enfants dont j'ai besoin, je vais pouvoir organiser mon
code comme ceci:
export const MyCard = ({ children }) => (
<div className="card">
<div className="header">{filterChildren(children, "header")}</div>
<div className="content">{filterChildren(children, "content")}</div>
<div className="footer">{filterChildren(children, "footer")}</div>
</div>
);
Et l'utilisation de ce composant pourrait ressembler à:
<MyCard>
<MyCard.header>
<h3>title</h3>
</MyCard.header>
<MyCard.content>
<p>Lorem ipsum</p>
</MyCard.content>
<MyCard.footer>
<button>Click me</button>
</MyCard.footer>
</MyCard>
Je trouve après coup que cela compléxifie un peu le composant Card
et que le filterChildren
n'est pas forcement
la meilleur des solutions.
Séparer la structure
Une autre approche, peut être plus simple, est de séparer le composant en plusieurs composants
export const MyCard = ({ children }) => {
return <div className="card">{children}</div>;
};
export const MyCardHeader = ({ children }) => {
return <div className="header">{children}</div>;
};
export const MyCardContent = ({ children }) => {
return <div className="content">{children}</div>;
};
export const MyCardFooter = ({ children }) => {
return <div className="footer">{children}</div>;
};
L'utilisation de cette solution ressemblerai à ceci:
<MyCard>
<MyCardHeader>
<h3>title</h3>
</MyCardHeader>
<MyCardContent>
<p>Lorem ipsum</p>
</MyCardContent>
<MyCardFooter>
<button>Click me</button>
</MyCardFooter>
</MyCard>
La structure de MyCard
n'est plus décidé par MyCard
lui même mais par son utilisateur
Le composant devient uniquement un container qui sert à afficher sa bordure, son fond et ses marges
Le code de MyCard
devient plus simple, mais son utilisation est plus libre et laisse la possibilité de faire plus d'erreurs
Conclusion
Je ne pense pas qu'il y ai de meilleur solution parmi les 3 que j'ai découvertes.
Cela dépend surtout de l'endroit ou l'on veut placer la responsabilité et la complexité.