Tipado de Props
Receta
Define contratos de prop claros y seguros para tus componentes React usando interfaces y alias de tipo de TypeScript. Cubre props requeridas, props opcionales, children, y patrones de props comunes.
Ejemplo Funcional
// Props básicos con campos requeridos y opcionales
type ButtonProps = {
label: string;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
onClick: () => void;
};
export function Button({ label, variant = "primary", disabled = false, onClick }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} disabled={disabled} onClick={onClick}>
{label}
</button>
);
}// Tipado de children
type CardProps = {
title: string;
children: React.ReactNode;
};
export function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}// Patrón render prop
type DataListProps<T> = {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
};
export function DataList<T>({ items, renderItem }: DataListProps<T>) {
return <ul>{items.map((item, i) => <li key={i}>{renderItem(item, i)}</li>)}</ul>;
}// Uso
<Button label="Submit" onClick={() => console.log("clicked")} />
<Card title="Welcome"><p>Hello world</p></Card>
<DataList items={["a", "b"]} renderItem={(item) => <span>{item}</span>} />Buceo Profundo
Cómo Funciona
- Los tipos de props actúan como un contrato entre el componente y sus consumidores. TypeScript fuerza que cada prop requerido sea suministrado y cada valor coincida con el tipo esperado.
React.ReactNodees el tipo children más amplio. Acepta strings, números, elementos JSX, arrays, fragmentos,null, yundefined.React.ReactElementes más estrecho queReactNode. Úsalo cuando específicamente necesites un elemento JSX (no un string o número).- Los valores por defecto en la desestructuración (por ejemplo,
variant = "primary") funcionan perfectamente con props opcionales. TypeScript infiere el tipo estrechado dentro del cuerpo de la función. - Los tipos literales de unión como
"primary" | "secondary"te dan autocompletado y atrapan typos en tiempo de compilación.
Variaciones
Extendiendo props de elementos HTML:
type InputProps = React.ComponentPropsWithoutRef<"input"> & {
label: string;
error?: string;
};
export function Input({ label, error, ...rest }: InputProps) {
return (
<div>
<label>{label}</label>
<input {...rest} />
{error && <span className="error">{error}</span>}
</div>
);
}Props con inyección de componentes:
type LayoutProps = {
as?: React.ElementType;
children: React.ReactNode;
className?: string;
};
export function Layout({ as: Component = "div", children, className }: LayoutProps) {
return <Component className={className}>{children}</Component>;
}Notas de TypeScript
- Usa
typepara props que usan uniones o intersecciones. Usainterfacecuando necesitesextendso quieras declaration merging. React.PropsWithChildren<T>es una abreviatura que añadechildren?: React.ReactNodea tu tipo.React.ComponentPropsWithRef<"div">incluye el propref;ComponentPropsWithoutRef<"div">lo excluye.
Trampas
- Usar
React.FCañade un propchildrenimplícito en tipos de React antiguos (pre-18). En tipos de React 18+,React.FCya no incluyechildrenimplícitamente. - Tipear children como
JSX.Elementrechazará strings, números, y arrays. UsaReact.ReactNodea menos que tengas una razón específica. - Extender
...resta un elemento DOM sin filtrar props personalizados causa advertencias de React sobre atributos DOM desconocidos. - Olvidar exportar tu tipo de props hace imposible que los consumidores lo reutilicen o lo extiendan.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
Alias type | Soporta uniones, intersecciones, tipos mapeados | Sin declaration merging |
interface | Extensible, patrón OOP familiar | No puede expresar tipos de unión directamente |
React.FC<Props> | Anotación de tipo de retorno explícita | Verboso, sin soporte para componentes genéricos |
| Tipos de props inline | Rápido para componentes desechables | Difícil de reutilizar o exportar |
PropsWithChildren | Abreviatura conveniente para children | Oculta el prop children de los lectores |
FAQs
¿Deberías usar type o interface para props de componentes?
- Usa
typecuando necesites uniones, intersecciones, o tipos mapeados. - Usa
interfacecuando necesitesextendso declaration merging. - Para la mayoría de componentes React,
typees suficiente y más flexible.
¿Cuál es la diferencia entre React.ReactNode y React.ReactElement para children?
ReactNodeacepta strings, números, elementos JSX, arrays, fragmentos,null, yundefined.ReactElementsolo acepta elementos JSX (no strings o números).- Usa
ReactNodea menos que específicamente necesites restringir children a elementos JSX.
¿Cómo extiendes los props nativos de elementos HTML para un componente personalizado?
type InputProps = React.ComponentPropsWithoutRef<"input"> & {
label: string;
error?: string;
};
function Input({ label, error, ...rest }: InputProps) {
return (
<div>
<label>{label}</label>
<input {...rest} />
{error && <span>{error}</span>}
</div>
);
}- Usa
ComponentPropsWithoutRef<"element">para obtener todos los props nativos. - Intersecta con tus props personalizados usando
&.
Trampa: ¿Qué sucede si extiendes ...rest a un elemento DOM sin filtrar props personalizados?
- React advertirá sobre atributos DOM desconocidos.
- Los props personalizados como
labeloerrorse pasan al elemento HTML. - Desestructura los props personalizados antes de extender el resto.
¿Cómo funcionan los valores por defecto con props opcionales en TypeScript?
type ButtonProps = {
variant?: "primary" | "secondary";
};
function Button({ variant = "primary" }: ButtonProps) {
// TypeScript estrecha variant a "primary" | "secondary" (no undefined)
}- Marca el prop como opcional con
?en el tipo. - Proporciona el valor por defecto en el patrón de desestructuración.
- TypeScript infiere el tipo estrechado dentro del cuerpo de la función.
¿Qué es React.PropsWithChildren<T> y cuándo deberías usarlo?
- Una abreviatura que añade
children?: React.ReactNodea tu tipo. - Conveniente pero oculta el prop
childrende los lectores de la definición de tipo. - Prefiere explícitamente
children: React.ReactNodeen el tipo para claridad.
Trampa: ¿Incluye React.FC children en su tipo?
- En tipos de React 18+,
React.FCya no incluyechildrenimplícitamente. - En tipos de React antiguos (pre-18), añadía un prop
childrenimplícito. - Si necesitas children, decláralo explícitamente en tu tipo de props.
¿Cómo tipeas un render prop?
type DataListProps<T> = {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
};- El render prop es una función que devuelve
React.ReactNode. - Usa un genérico para tipear el parámetro del item basándote en los datos.
¿Cuál es el patrón del prop as para componentes polimórficos?
type LayoutProps = {
as?: React.ElementType;
children: React.ReactNode;
};
function Layout({ as: Component = "div", children }: LayoutProps) {
return <Component>{children}</Component>;
}React.ElementTypeacepta etiquetas string ("div","span") y tipos de componentes.- El valor por defecto es típicamente un elemento HTML semántico.
¿Por qué deberías exportar tu tipo de props?
- Los consumidores pueden necesitar reutilizar o extender el tipo de props de tu componente.
- Sin la exportación, no pueden construir componentes envolventes o extraer subconjuntos de props.
- Siempre exporta tipos de props para componentes compartidos o de librería.