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.
---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.
---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.
---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.