Saltar al contenido

Props opcionales, valores por defecto y rest

Cómo marcar props opcionales con el modificador de interrogación, dar valores por defecto al desestructurar Astro.props, recoger props sobrantes con rest, y reenviarlas a un elemento con spread para construir componentes envoltorio transparentes.

Props opcionales: el modificador de interrogación

Por defecto, cada prop de una interface Props es obligatoria: omitirla es un error de tipos. Para declarar que una prop puede faltar, se añade el modificador ? a su nombre. El tipo resultante incluye undefined, y el compilador deja de exigirla.

ASTRO
---interface Props {  titulo: string;  subtitulo?: string;}const { titulo, subtitulo } = Astro.props;---<h2>{titulo}</h2>{subtitulo && <p>{subtitulo}</p>}

Marcar subtitulo como opcional tiene una consecuencia inmediata en el hijo: su tipo es string | undefined, así que no puedes tratarlo como texto sin comprobar antes que existe. Por eso el ejemplo lo protege con subtitulo && ..., que solo renderiza el párrafo cuando hay valor. Lo opcional en el contrato obliga a lo defensivo en el uso.

Valores por defecto: la red de seguridad

Una prop opcional que llega como undefined deja un hueco. A menudo prefieres un valor sensato en su lugar, y el sitio idiomático para fijarlo es la propia desestructuración de Astro.props.

ASTRO
---interface Props {  variante?: 'solida' | 'fantasma';  nivel?: number;}const { variante = 'solida', nivel = 1 } = Astro.props;---<div class={`btn-${variante}`} data-nivel={nivel}>  <slot /></div>

El valor por defecto se aplica exactamente cuando la prop es undefined, ni antes ni después. Si el padre pasa variante="fantasma", gana su valor; si la omite, entra solida. Combinar el ? en la interfaz con el = en la desestructuración es el patrón canónico: la interfaz declara que la prop puede faltar, y la desestructuración decide qué ocurre cuando falta.

Rest: recoger lo que no nombras

A veces un componente no quiere enumerar todas las props que acepta. Un botón envoltorio, por ejemplo, debería admitir cualquier atributo válido de un botón nativo —type, disabled, aria-label— sin listarlos uno a uno. La sintaxis rest recoge en un solo objeto todas las props que no desestructuraste por nombre.

ASTRO
---import type { HTMLAttributes } from 'astro/types';interface Props extends HTMLAttributes<'button'> {  variante?: 'solida' | 'fantasma';}const { variante = 'solida', class: clase, ...resto } = Astro.props;---<button class:list={[`btn-${variante}`, clase]} {...resto}>  <slot /></button>

Dos piezas se combinan aquí. Extender HTMLAttributes<'button'> hace que la interfaz herede todos los atributos legítimos de un botón, tipados; así el padre recibe autocompletado de disabled o type sin que tú los declares. Y ...resto captura justo esos atributos heredados que no extrajiste a mano, dejándolos listos para reenviar.

Spread: reenviar props a un elemento

Recoger props con rest solo es útil si luego las colocas en algún sitio. El operador spread {...resto} las derrama sobre un elemento, convirtiendo cada clave del objeto en un atributo. Es la mitad receptora del patrón: lo que rest recoge, spread reparte.

El resultado es un envoltorio transparente: un componente que añade su valor —la clase btn-solida, la lógica de la variante— sin bloquear los atributos nativos del elemento que envuelve. El padre lo usa casi como usaría un <button> normal, pero con las mejoras que el componente aporta. La clase se fusiona con class:list, que combina la clase propia del componente con la que venga de fuera sin que una pise a la otra.

Este patrón escala a cualquier elemento: un <input> que reenvía sus atributos, un enlace que hereda los de un ancla, una imagen que acepta los de <img>. En todos, la receta es la misma: extiende HTMLAttributes del elemento, extrae por nombre lo que el componente gestiona, y reenvía el ...resto con spread.