¿Cómo tipas `children` en un componente y para qué sirve la composición en React?
Ver respuesta — intenta responderla en voz alta primero
children se tipa como ReactNode, el tipo más amplio: acepta JSX, strings, números, arrays o null. La composición consiste en armar interfaces metiendo componentes dentro de otros, en vez de crear componentes gigantes llenos de props.
Cuando escribes <Tarjeta>Hola</Tarjeta>, lo que va entre las etiquetas llega al componente como la prop children. Es la base de la composición: el contenedor decide el marco (borde, padding, título) y quien lo usa decide el contenido.
Los puntos clave:
ReactNodees el tipo correcto parachildren: cubre todo lo que React puede renderizar (elementos JSX, strings, números, arrays,null,undefined).- No solo
children: cualquier prop puede recibir un componente. Por ejemplo,icono: ReactNodete deja pasar<IconoEstrella />a una tarjeta. - Diferencia entre tipos parecidos:
ReactNode: cualquier cosa renderizable. Úsalo casi siempre.ReactElement: solo un elemento JSX (<div />,<Boton />). Un string ya no cabe. Úsalo cuando exiges JSX de verdad.string: solo texto plano. Útil si vas a manipular el contenido como texto.
- La composición evita el "componente Dios" con 20 props: el contenedor recibe
childreny cada pantalla lo llena distinto.
import type { ReactNode, ReactElement } from "react";
// Componente contenedor: pone el marco, no decide el contenido
type TarjetaProps = {
titulo: string; // solo texto: lo usamos tal cual
icono?: ReactNode; // un componente pasado por props
children: ReactNode; // cualquier cosa renderizable
};
function Tarjeta({ titulo, icono, children }: TarjetaProps) {
return (
<section className="tarjeta">
<header>{icono}<h2>{titulo}</h2></header>
<div className="tarjeta-cuerpo">{children}</div>
</section>
);
}
// ReactElement: aquí exigimos JSX, un string no compila
function Modal({ contenido }: { contenido: ReactElement }) {
return <div className="modal">{contenido}</div>;
}
// Uso: el mismo contenedor sirve para contenidos muy distintos
// <Tarjeta titulo="Perfil" icono={<span aria-hidden>*</span>}>
// <p>Nombre: Ana</p>
// </Tarjeta>
// <Tarjeta titulo="Aviso">Solo un texto simple</Tarjeta>
// <Modal contenido={<p>Confirmar acción</p>} />
// <Modal contenido="hola" />
// Error: Type 'string' is not assignable to type 'ReactElement'
Tipar children como string o como JSX.Element y quedarse corto:
type TarjetaProps = { children: string }; // demasiado restrictivo
// <Tarjeta><p>Hola</p></Tarjeta>
// Error: Type 'Element' is not assignable to type 'string'
Con string no puedes pasar JSX, y con ReactElement no puedes pasar texto suelto ni varios hijos con libertad. La corrección es usar el tipo pensado justo para eso:
import type { ReactNode } from "react";
type TarjetaProps = { children: ReactNode }; // acepta texto, JSX, arrays, null...
// Regla mental: para children, ReactNode es la opción por defecto
"Yo tipo
childrencomoReactNode, porque es el tipo que cubre todo lo renderizable: JSX, strings, números, arrays onull.ReactElementes más estricto, solo acepta elementos JSX, y lo reservo para cuando de verdad exijo un elemento. La composición me sirve para no crear componentes con veinte props: hago contenedores como<Tarjeta>que ponen el marco y reciben el contenido porchildren, y si necesito huecos específicos, paso componentes por props, como unicono: ReactNode. Así reutilizo el contenedor en toda la app."
Crea un componente Panel que reciba children y una prop opcional piePagina (también renderizable). El pie solo se muestra si viene.
Ver solución
import type { ReactNode } from "react";
type PanelProps = {
children: ReactNode;
piePagina?: ReactNode;
};
function Panel({ children, piePagina }: PanelProps) {
return (
<section className="panel">
<div className="panel-cuerpo">{children}</div>
{piePagina && <footer className="panel-pie">{piePagina}</footer>}
</section>
);
}
// Uso: <Panel piePagina={<small>Actualizado hoy</small>}>...</Panel>