Aujourd’hui, je vous propose de découvrir un motif de développement avec React pour rendre vos composants plus flexibles : l’utilisation d'une propriété asChild. Ce pattern, utilisé notamment dans la librairie Radix UI, permet de déléguer le rendu et les props à un enfant, tout en gardant la logique du composant parent.
Le problème avec les composants polymorphiques
Prenons un exemple simple : un composant Button. Dans une application, on a souvent besoin de créer des éléments qui ressemblent à des boutons, mais qui ne sont pas forcément des <button> (comme par exemple un lien <a>).
La première approche consisterait à ajouter une prop as à notre composant, qui permettrait d’indiquer si l’on souhaite rendre un <a>, un <div>, etc.
Le problème de cette approche, c’est qu’elle complexifie énormément la gestion des props et leur typage dans TypeScript. Il faut en effet gérer toutes les props que l’élément choisi pourrait recevoir (comme href dans le cas d’un lien), ce qui peut rapidement devenir lourd.
L’approche asChild
Une autre solution, beaucoup plus simple à maintenir, consiste à introduire une prop asChild qui permettra d'indiquer que l'on souhaite transférer les attributs à l'élément enfant.
Implémentation d’un composant Slot
Pour implémenter ce comportement, on va créer un composant générique qui servira à transférer les propriétés reçues à l'élément enfant.
Ici, on clone l’enfant en lui injectant toutes les props reçues par le composant parent. Attention cependant à bien fusionner certaines props comme className ou style pour éviter d’écraser celles de l’enfant.
Intégration dans notre Button
Dans notre composant Button, il suffit ensuite d’utiliser ce Slot :
Des cas concrets
Cette approche fonctionne très bien dans des cas un peu complexes comme par exemple la création d'un bouton d’upload de fichier (pour lequel on va utiliser un label comme bouton).
Le style sera appliqué au label, et le comportement d’ouverture de fichier sera conservé.
Limites de cette approche
Malheureusement tout n'est pas parfait avec cette approche et il y a quelques petit défaut à savoir avant de l'adopter :
- Il n'est pas possible de vérifier le type de l'enfant de manière statique. Dans notre implémentation si un composant avec
asChildreçoit plusieurs enfant on autre une erreur à l'éxécution seulement. - L'enfant doit accepter les props qui lui seront envoyées par le composant utilisant
asChildau risque d'avoir un comportement partiel.
Avec un peu de rigueur, ces limites ne posent généralement pas de problème.