Tipos Utilitarios para React
Receta
Usa los tipos utilitarios integrados de TypeScript para transformar y componer tipos de props de React. Deriva nuevos tipos de tipos existentes en lugar de duplicar definiciones.
Ejemplo de Funcionamiento
// Base type
type User = {
id: string;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
createdAt: Date;
};
// Partial - all fields become optional (great for update forms)
type UpdateUserPayload = Partial<User>;
// Pick - select specific fields
type UserPreview = Pick<User, "id" | "name">;
// Omit - exclude specific fields
type CreateUserPayload = Omit<User, "id" | "createdAt">;
// Record - typed key-value map
type RolePermissions = Record<User["role"], string[]>;
const permissions: RolePermissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
};// Practical component using derived types
type UserFormProps = {
initialData?: Partial<User>;
onSubmit: (data: CreateUserPayload) => void;
};
function UserForm({ initialData, onSubmit }: UserFormProps) {
const [form, setForm] = useState<CreateUserPayload>({
name: initialData?.name ?? "",
email: initialData?.email ?? "",
role: initialData?.role ?? "viewer",
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
onSubmit(form);
}}
>
<input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
/>
<input
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
/>
<button type="submit">Save</button>
</form>
);
}Análisis Profundo
Cómo Funciona
Partial<T>hace que todas las propiedades enTsean opcionales. Útil para payloads de actualización donde solo cambias algunos campos.Required<T>es el inverso dePartial— hace que todas las propiedades sean requeridas. Útil para normalizar objetos de configuración con valores por defecto.Pick<T, K>crea un tipo solo con las claves especificadas deT. Úsalo para crear sub-tipos enfocados.Omit<T, K>crea un tipo con todas las claves excepto las especificadas. Común para payloads de creación donde el servidor genera elid.Record<K, V>crea un tipo de objeto con claves de tipoKy valores de tipoV. Perfecto para mapas de búsqueda.Extract<T, U>extrae miembros de una unión que coinciden conU.Extract<User["role"], "admin" | "editor">devuelve"admin" | "editor".Exclude<T, U>elimina miembros de una unión que coinciden conU. El inverso deExtract.
Variaciones
Extrayendo tipos de props de componentes:
// Get props from an existing component
type ButtonProps = React.ComponentProps<typeof Button>;
// Get props of an HTML element
type DivProps = React.ComponentPropsWithoutRef<"div">;
// Extend HTML element props
type CardProps = React.ComponentPropsWithoutRef<"div"> & {
title: string;
elevated?: boolean;
};Props de solo lectura:
type ImmutableUser = Readonly<User>;
// All fields are now readonly - prevents accidental mutation
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};Tipos mapeados para estado de formulario:
type FormErrors<T> = Partial<Record<keyof T, string>>;
type UserFormErrors = FormErrors<CreateUserPayload>;
// { name?: string; email?: string; role?: string }NonNullable para eliminar null/undefined:
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // UserNotas de TypeScript
- Los tipos utilitarios se componen:
Partial<Pick<User, "name" | "email">>te da{ name?: string; email?: string }. React.ComponentProps<typeof MyComponent>extrae el tipo de props de cualquier componente, incluso de terceros donde el tipo no está exportado.Parameters<T>yReturnType<T>funcionan en tipos de funciones.ReturnType<typeof useAuth>te da el tipo de retorno de un hook personalizado.
Errores Comunes
Omitno genera error en claves que no existen en el tipo original.Omit<User, "nonExistent">silenciosamente devuelve el tipo completoUser.Partialhace que todo sea opcional, incluso campos que deberían permanecer requeridos. UsaPartial<Pick<T, K>> & Omit<T, K>para hacer solo algunos campos opcionales.Record<string, T>permite cualquier clave de string. Esto puede ocultar errores tipográficos. Prefiere claves de unión literal cuando el conjunto de claves se conoce.- Los objetos profundamente anidados no se ven afectados por
PartialoReadonly. Estos solo operan en propiedades de nivel superior. Necesitas tipos recursivos personalizados para transformaciones profundas.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
Partial<T> para actualizaciones | Derivado del tipo fuente, se mantiene sincronizado | Hace todos los campos opcionales, no solo algunos |
| Sub-tipos manuales | Control total sobre cada tipo | Duplica definiciones, se desvía de la fuente |
Composición de Pick / Omit | Selección precisa de campos | Las composiciones profundamente anidadas son difíciles de leer |
.pick() / .omit() de Zod | Validación en tiempo de ejecución + inferencia de tipos | Requiere dependencia de Zod |
Operador satisfies | Valida la forma sin ampliar | No crea un tipo reutilizable |
FAQs
¿Qué hace Partial<T> y cuándo debo usarlo?
- Hace que todas las propiedades en
Tsean opcionales. - Úsalo para payloads de actualización donde solo cambias algunos campos, por ejemplo,
Partial<User>para un cuerpo de solicitud PATCH.
¿Cuál es la diferencia entre Pick y Omit?
Pick<T, K>crea un tipo solo con las claves especificadas deT.Omit<T, K>crea un tipo con todas las claves excepto las especificadas.- Usa
Pickcuando quieres un pequeño subconjunto; usaOmitcuando quieres la mayoría de campos menos unos pocos.
¿Cómo extraigo el tipo de props de un componente existente?
type ButtonProps = React.ComponentProps<typeof Button>;
type DivProps = React.ComponentPropsWithoutRef<"div">;- Funciona tanto en componentes personalizados como en elementos HTML.
¿Qué hace Record<K, V>?
- Crea un tipo de objeto con claves de tipo
Ky valores de tipoV. - Ejemplo:
Record<User["role"], string[]>mapea cada rol a un array de strings de permisos.
¿Cuál es la diferencia entre Extract y Exclude?
Extract<T, U>mantiene miembros de la unión que coinciden conU.Exclude<T, U>elimina miembros de la unión que coinciden conU.- Ejemplo:
Extract<"admin" | "editor" | "viewer", "admin" | "editor">devuelve"admin" | "editor".
¿Puedo componer tipos utilitarios juntos?
- Sí. Se componen libremente:
Partial<Pick<User, "name" | "email">>da{ name?: string; email?: string }. - La composición mantiene los tipos derivados de una única fuente de verdad.
Error Común: ¿Genera error Omit si especifico una clave que no existe en el tipo?
- No.
Omit<User, "nonExistent">silenciosamente devuelve el tipo completoUser. - TypeScript no te advierte, así que verifica tus nombres de claves.
Error Común: ¿Funciona Partial en objetos profundamente anidados?
- No.
Partialsolo afecta propiedades de nivel superior. - Para opcionalidad profunda, necesitas un tipo recursivo personalizado como
DeepPartial<T>.
¿Cómo hago que solo algunos campos sean opcionales mientras mantengo otros requeridos?
type UpdateUser = Partial<Pick<User, "name" | "email">> & Omit<User, "name" | "email">;- Combina
Partial<Pick<>>para los campos opcionales conOmit<>para el resto.
¿Para qué se usa NonNullable<T>?
- Elimina
nullyundefinedde un tipo. NonNullable<User | null | undefined>daUser.
¿Cómo obtengo el tipo de retorno de un hook personalizado?
type AuthData = ReturnType<typeof useAuth>;ReturnType<T>funciona en cualquier tipo de función, incluyendo hooks y funciones de utilidad.
¿Qué es un tipo mapeado y cómo FormErrors lo usa?
type FormErrors<T> = Partial<Record<keyof T, string>>;Record<keyof T, string>mapea cada clave deTa un valorstring.- Envolverlo en
Partialhace que cada mensaje de error sea opcional.