Skip to content
This page has been auto-translated and may contain errors.View in English

Children y composición

HTML siempre ha funcionado anidando: un <select> envuelve sus <option>s, un <ul> envuelve sus <li>s, un <button> envuelve su etiqueta. El elemento del medio es hijo del elemento que lo rodea, y los dos trabajan juntos para hacer lo que ves. Los componentes de React pueden funcionar igual. En lugar de pasar todo a través de props nombradas, un componente puede envolver contenido de la misma forma que los elementos nativos lo hacen, y esa única idea es lo que separa un componente que configuras de un componente que compones.

Este capítulo desarrolla la idea de la forma que lo hace el curso, a través de un Button para una librería de componentes: primero children, luego prop spreading, y después los detalles finales que hacen un componente reutilizable en la práctica.

Children como interfaz

El capítulo Props introdujo children: el contenido entre las etiquetas de apertura y cierre de un componente llega como una prop con ese nombre. Lo que te ganas para reutilización es más de lo que parece.

jsx
function Button({ children }) {
  return <button>{children}</button>
}

// uso: <Button>Comprar ahora</Button>

El componente decide dónde renderizar los children, y quien lo usa decide qué son. Compáralo con una prop text, que funciona hasta que quien la usa quiere algo más allá de una cadena. Con children, nada detiene al usuario de pasar más que texto:

jsx
// uso:
// <Button>
//   <CartIcon />
//   Comprar ahora
// </Button>

Un icono a la izquierda de la etiqueta, sin prop icon, sin opción iconPosition="left", sin anticipación del autor del componente en absoluto. ¿Quieres el icono a la derecha? Muévelo al otro lado del texto. Cada prop de configuración que el autor habría tenido que inventar se disuelve en quien la usa escribiendo el markup que quiso.

Reenviando el resto de las props

Un Button que renderiza un <button> nativo tiene un segundo problema: eventos. Cuando quien lo usa escribe <Button onClick={...}>, ese onClick es una prop personalizada que llega a tu componente; nada sucede a menos que el componente la reenvíe al verdadero <button> debajo. Podrías reenviar onClick a mano, luego onDoubleClick, luego style, luego className, y aún así nunca cubrirías todo lo que un button nativo acepta. La sintaxis spread los reenvía todos a la vez:

jsx
function Button({ children, ...rest }) {
  return <button {...rest}>{children}</button>
}

Desestructura las props que tu componente realmente maneja, recopila todo lo demás con sintaxis rest, y expande el rest sobre el elemento subyacente. Cada atributo válido de button que quien la usa pase, incluyendo manejadores de eventos, va donde debe ir. Las props que tu componente inventa para sí mismo (una variant, digamos) se desestructuran para que nunca lleguen al elemento del DOM.

Haciendo espacio para tu propia API

Los componentes reutilizables usualmente agregan opciones encima del elemento nativo. El button del curso toma un size ("sm" o "lg") y una variant ("success", "warning", "danger"), cada uno mapeando a una clase CSS:

jsx
function Button({ children, size, variant, className, ...rest }) {
  const sizeClass = size ? `button-${size}` : ''
  const variantClass = variant ? `button-${variant}` : ''
  const allClasses = [sizeClass, variantClass, className].filter(Boolean).join(' ')

  return (
    <button className={allClasses} {...rest}>
      {children}
    </button>
  )
}

Esa fusión evita un bug sutil. Si el componente establece className y quien lo usa también pasa una, lo que llega último al elemento gana: expande el rest después de tu className y la clase de quien la usa sobrescribe silenciosamente la tuya; expándelo antes y la tuya sobrescribe la de ellos. Desestructurar className y fusionar las cadenas tú mismo es la solución. Librerías como clsx o classnames existen para hacer exactamente esta fusión una vez que las condiciones se multiplican.

Expandir rest props es un contrato, y tiene bordes afilados. Los dos que la sección ya mostró, orden de spread y props que se filtran, son los baratos, aunque la filtración es más silenciosa de lo que la mayoría espera.

Qué hace realmente una prop filtrada

Una prop en lowercase como variant llega al nodo del DOM como un atributo HTML inválido sin advertencia, que es el caso que muerde. React advierte sobre una prop camelCase como iconPosition y aún la renderiza; advierte y renderiza nada para un valor booleano o un nombre de manejador todo en lowercase como onclick. Un typo camelCase como onClik no obtiene ni el atributo ni la advertencia.

ref corta del otro lado: en React 19 es una prop ordinaria, así que viaja en ...rest para componentes de función y llega al elemento subyacente sin forwardRef. El borde caro es superficie de API. Una vez que quienes la usan pueden pasar props nativas arbitrarias, lo harán, y cada una que usen se convierte en algo que les debes en la siguiente versión; estrechar el passthrough más tarde es un cambio que rompe compatibilidad aunque nunca lo documentaras.

El "mega challenge" de Avatar del curso muestra el otro lado de la composición: un componente que renderiza de tres formas diferentes, una foto cuando se proporciona src, iniciales cuando hay children, un icono en otro caso. La sobrecarga como esa conviene a una librería de componentes documentada, donde un Avatar flexible vence a tres componentes casi idénticos. En código de aplicación, buscarla temprano usualmente señala un componente haciendo demasiados trabajos; dos componentes pequeños vencen a uno ingenioso. El tirón entre esos dos instintos, agregar una prop o componer desde afuera, corre a través del resto de esta sección, y los próximos patrones, compound components y render props, son respuestas estructuradas a eso.

JunoDeja que quienes la usan pongan cosas dentro Un Button que renderiza sus children no necesita una prop text, una prop icon, y una prop icon-position. Quien la use escribe el icono y el texto dentro de las etiquetas en el orden que quiere, y el Button deja ese contenido donde corresponde.

Anidamiento, la cosa que HTML siempre ha hecho, es también el mejor truco de React para reutilización.

JunoDeja que quienes la usan pongan cosas dentro Construye componentes reutilizables sobre dos hábitos: renderiza children donde va el contenido, y desestructura las props que manejas mientras expandes ...rest sobre el elemento subyacente para que atributos nativos y manejadores de eventos pasen.

Fusiona className tú mismo en lugar de dejar que el orden de spread decida cuál sobrevive, y mantén props inventadas como variant fuera del objeto rest para que nunca golpeen el DOM.

JunoDeja que quienes la usan pongan cosas dentro Trata el forwarding de rest-props como parte de la superficie de API de tu componente: las colisiones se resuelven por orden de spread, las props personalizadas no desestructuradas se filtran al DOM, y quienes la usan dependerán del passthrough.

Prefiere composición a través de children sobre props de configuración hasta que una prop se gane su lugar, y reserva componentes sobrecargados para código de librería documentado donde una pieza realmente flexible reemplaza varias.

Siguiente: Compound components, donde un componente se convierte en varios que trabajan juntos.