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

Hijos y composición ​

El HTML siempre ha funcionado por anidamiento: un <select> envuelve sus <option>, un <ul> envuelve sus <li>, un <button> envuelve su etiqueta. El elemento del medio es hijo del elemento que lo rodea, y ambos trabajan juntos para formar lo que ves. Los componentes de React pueden funcionar de la misma manera. En lugar de pasar todo mediante props con nombre, un componente puede envolver contenido igual que hacen los elementos nativos, y esa sola idea es lo que separa a un componente que configuras de un componente que compones.

Este capítulo construye el argumento de la misma forma en que lo hace el curso, a través de un Button para una librería de componentes: primero los children, después el spreading de props, y por último los detalles que hacen que un componente sea reutilizable en la práctica.

Children como interfaz ​

El capítulo de Props presentó children: el contenido entre la etiqueta de apertura y cierre de un componente llega como una prop con ese nombre. Lo que esto te da para la reutilización es más grande de lo que parece.

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

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

El componente decide dónde se renderizan los children, y quien lo usa decide qué son. Compara eso con una prop text, que funciona bien hasta que quien la usa quiere algo más que un string. Con children, no hay nada que impida pasar más que solo texto:

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

Un ícono a la izquierda de la etiqueta, sin una prop icon, sin una opción iconPosition="left", sin que quien escribió el componente haya tenido que anticipar nada. ¿Quieres el ícono 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 el hecho de que quien llama al componente simplemente escribe el markup que tenía en mente.

Reenviando el resto de las props ​

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

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

Desestructura las props que tu componente realmente maneja, junta todo lo demás con la sintaxis rest, y esparce (spread) el resto sobre el elemento subyacente. Cada atributo válido de botón que pase quien lo usa, incluidos los manejadores de eventos, termina donde corresponde. Las props que tu componente inventa por su cuenta (digamos, un variant) se desestructuran aparte para que nunca lleguen al elemento del DOM.

Haciendo espacio para tu propia API ​

Los componentes reutilizables suelen agregar opciones por encima del elemento nativo. El botón del curso recibe un size ("sm" o "lg") y un variant ("success", "warning", "danger"), cada uno mapeado 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 define className y quien lo usa también pasa uno, el que llegue de último al elemento es el que gana: si esparces el resto después de tu propio className, la clase de quien llama sobrescribe la tuya en silencio; si lo esparces antes, la tuya sobrescribe la de ellos. La solución es desestructurar className aparte y fusionar los strings tú mismo. Librerías como clsx o classnames existen justamente para hacer esta fusión una vez que las condiciones se multiplican.

Esparcir las rest props es un contrato, y tiene sus puntos delicados. Los dos que ya mostró la sección, el orden del spread y las props filtradas, son los más baratos, aunque la filtración es más silenciosa de lo que la mayoría espera.

Lo que realmente hace una prop filtrada

Una prop de tipo string en minúsculas como variant termina en el nodo del DOM como un atributo HTML inválido, sin ninguna advertencia, y ese es el caso que realmente muerde. React advierte sobre una prop en camelCase como iconPosition y de todas formas la renderiza; advierte y no renderiza nada para un valor booleano o un nombre de manejador todo en minúsculas como onclick. Un typo en camelCase como onClik no recibe ni el atributo ni la advertencia.

ref funciona al revés: en React 19 es una prop ordinaria, así que viaja dentro de ...rest en los componentes de función y llega al elemento subyacente sin necesidad de forwardRef.

El punto delicado y costoso es la superficie de la API. Una vez que quienes llaman al componente 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; restringir el passthrough más adelante es un cambio disruptivo aunque nunca lo hayas documentado.

El "mega desafío" del Avatar del curso muestra el otro lado de la composición: un solo componente que se renderiza de tres formas distintas, una foto cuando se provee src, iniciales cuando se proveen children, un ícono en cualquier otro caso. Una sobrecarga así le sienta bien a una librería de componentes documentada, donde un Avatar flexible es mejor que tres componentes casi idénticos. En código de aplicación, recurrir a esto demasiado pronto suele ser señal de un componente que hace demasiadas cosas; dos componentes pequeños son mejores que uno ingenioso.

La tensión entre esos dos instintos, agregar una prop o componer desde afuera, recorre el resto de esta sección, y los siguientes patrones, componentes compuestos y render props, son respuestas estructuradas a esa tensión.

JunoDeja que quien usa el componente ponga cosas adentro Un Button que renderiza sus children no necesita una prop de texto, una prop de ícono y una prop de posición del ícono. Quien lo usa escribe el ícono y el texto dentro de las etiquetas en el orden que quiera, y el Button coloca ese contenido donde corresponde.

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

JunoDeja que quien usa el componente ponga cosas adentro Construye componentes reutilizables sobre dos hábitos: renderiza children donde va el contenido, y desestructura las props que manejas mientras esparces ...rest sobre el elemento subyacente para que los atributos nativos y los manejadores de eventos pasen a través.

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

JunoDeja que quien usa el componente ponga cosas adentro Trata el reenvío de rest props como parte de la superficie de la API de tu componente: las colisiones se resuelven según el orden del spread, las props personalizadas que no desestructuras se filtran al DOM, y quienes usan el componente van a depender de ese passthrough.

Prefiere la composición mediante children en lugar de props de configuración hasta que una prop se gane su lugar, y reserva los componentes sobrecargados para código de librería documentado, donde una sola pieza flexible realmente puede reemplazar a varias.

A continuación: Componentes compuestos, donde un componente se convierte en varios que trabajan juntos.