React
Composición con children y slots
Explica cómo componer componentes con children, props de renderizado y slots explícitos para distribuir contenido sin depender de herencia ni APIs rígidas.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
React
Explica cómo componer componentes con children, props de renderizado y slots explícitos para distribuir contenido sin depender de herencia ni APIs rígidas.
La composición permite construir componentes flexibles combinando estructura, datos y contenido recibido desde fuera. En lugar de heredar interfaz, un componente define un marco y permite que otros componentes ocupen regiones concretas.
children representa el contenido colocado entre las etiquetas de un componente:
type CardProps = {
children: React.ReactNode;
};
function Card({ children }: CardProps) {
return <section className="card">{children}</section>;
}<Card>
<h2>Pedido confirmado</h2>
<p>Tu compra fue registrada.</p>
</Card>Card controla el contenedor. El componente que lo utiliza controla el contenido. Esa separación permite reutilizar el mismo marco sin convertirlo en una colección interminable de props específicas.
Un componente rígido suele intentar anticipar todos los casos:
<Card
title="Pedido confirmado"
description="Tu compra fue registrada"
showIcon
showFooter
footerLabel="Cerrar"
/>Esta API puede funcionar para un caso concreto, pero se vuelve frágil cuando aparecen subtítulos, formularios, enlaces, estados de error o acciones múltiples.
La composición cambia la pregunta:
componente contenedor
├── controla estructura y comportamiento común
└── recibe contenido variable
↓
children o slotsEl componente no necesita conocer todas las variantes posibles del contenido.
Piensa en un componente compuesto como una plantilla con regiones abiertas:
Modal
├── Header slot
├── Body slot
└── Actions slotEl componente contenedor puede seguir controlando:
El consumidor decide qué elementos concretos aparecen en cada región.
children no siempre es un único elemento. Puede ser:
null o undefined.function Stack({ children }: { children: React.ReactNode }) {
return <div className="stack">{children}</div>;
}No asumas que puedes leer propiedades específicas de children como si fuera siempre un componente concreto. Cuando necesitas una API estructurada, usa props explícitas o componentes compuestos.
Cuando existen varias regiones con responsabilidades distintas, usa props de elementos:
type ModalProps = {
title: React.ReactNode;
children: React.ReactNode;
actions?: React.ReactNode;
};
function Modal({ title, children, actions }: ModalProps) {
return (
<section role="dialog" aria-modal="true">
<header>{title}</header>
<div>{children}</div>
{actions && <footer>{actions}</footer>}
</section>
);
}<Modal
title={<h2>Eliminar producto</h2>}
actions={
<>
<button type="button">Cancelar</button>
<button type="button">Eliminar</button>
</>
}
>
<p>Esta acción no se puede deshacer.</p>
</Modal>Los nombres de los slots comunican intención. title, actions o sidebar son más claros que una colección de children cuyo orden debe memorizarse.
Pasar datos hace que el hijo controle la representación:
<UserCard user={user} />Pasar elementos hace que el padre controle la representación de una región:
<Panel header={<UserHeader user={user} />} />No existe una opción universal. El trade-off está entre control y flexibilidad.
Una API puede expresarse mediante subcomponentes relacionados:
function Tabs({ children }: { children: React.ReactNode }) {
return <div>{children}</div>;
}
Tabs.List = function TabsList({ children }: { children: React.ReactNode }) {
return <div role="tablist">{children}</div>;
};
Tabs.Panel = function TabsPanel({ children }: { children: React.ReactNode }) {
return <div role="tabpanel">{children}</div>;
};Conceptualmente:
<Tabs>
<Tabs.List>...</Tabs.List>
<Tabs.Panel>...</Tabs.Panel>
</Tabs>Este patrón puede crear APIs expresivas, pero añade coordinación interna y expectativas sobre estructura. Debe usarse cuando las partes forman realmente una unidad, no solo para evitar props.
Una función como prop permite que el contenedor aporte datos al consumidor:
type DataListProps<T> = {
items: T[];
renderItem: (item: T) => React.ReactNode;
};
function DataList<T>({ items, renderItem }: DataListProps<T>) {
return <ul>{items.map((item) => renderItem(item))}</ul>;
}Este patrón es útil cuando el componente controla el proceso, pero el consumidor controla la representación. Sin embargo, puede hacer el JSX más complejo. Antes de usarlo, revisa si children, una prop de elemento o un custom Hook resuelven mejor el problema.
Una API basada en demasiados booleanos permite combinaciones ambiguas:
<Button primary large rounded loading danger />Mejor:
<Button variant="danger" size="large" isLoading />O una composición más explícita:
<Dialog>
<Dialog.Title>Eliminar producto</Dialog.Title>
<Dialog.Description>Esta acción no se puede deshacer.</Dialog.Description>
<Dialog.Actions>...</Dialog.Actions>
</Dialog>La composición no elimina todas las props. Las utiliza para decisiones claras y deja el contenido variable en regiones abiertas.
type DashboardLayoutProps = {
sidebar: React.ReactNode;
header: React.ReactNode;
children: React.ReactNode;
};
function DashboardLayout({
sidebar,
header,
children,
}: DashboardLayoutProps) {
return (
<div className="dashboard-layout">
<aside>{sidebar}</aside>
<div>
<header>{header}</header>
<main>{children}</main>
</div>
</div>
);
}<DashboardLayout
sidebar={<AdminNavigation />}
header={<AccountMenu user={user} />}
>
<OrdersOverview orders={orders} />
</DashboardLayout>Decide si el componente debe aceptar contenido vacío, renderizar un fallback o lanzar una advertencia durante desarrollo. No todos los contenedores necesitan hijos obligatorios.
Evita dejar wrappers vacíos que afecten el layout. Renderiza la región solo cuando exista contenido.
Si la API depende de un orden exacto, documenta y valida la estructura. Una API demasiado implícita puede ser difícil de usar.
Cuando el consumidor controla partes del markup, el contenedor debe seguir garantizando relaciones como aria-labelledby, roles y manejo de foco. Una API flexible no debe transferir silenciosamente toda la responsabilidad accesible al consumidor.
cloneElement puede modificar props de hijos, pero crea acoplamiento con su estructura y se considera una API poco común. Prefiere context, props explícitas o render props cuando comuniquen mejor la relación.
El consumidor no sabe qué hijo pertenece al header, body o footer. Usa slots nombrados.
El hijo pierde control de consistencia y semántica sin una necesidad real.
Las combinaciones crecen y aparecen estados imposibles.
Añaden funciones anidadas y complejidad cuando una prop simple sería suficiente.
Acopla el componente a tipos y props internas que pueden cambiar.
Una colección de subcomponentes diminutos puede volver difícil descubrir cómo usar el componente.
children****: una región principal abierta.children es una prop y puede contener múltiples formas de contenido.children y cuándo un slot nombrado?children sirve para una región principal; los slots nombrados para varias regiones distintas.Eventos en React explica cómo los componentes comunican acciones concretas y cómo esas acciones se diferencian de la sincronización mediante Effects.