La librería estándar de TypeScript incluye un conjunto de tipos genéricos de uso frecuente llamados utility types. Están disponibles de forma global sin necesidad de importar nada y cubren las transformaciones más comunes sobre tipos de objeto: hacer propiedades opcionales o requeridas, seleccionar o excluir un subconjunto, extraer información de funciones y clases, y gestionar valores posiblemente nulos. Esta guía los cubre todos con ejemplos reales.
Partial, Required y Readonly
interface Producto {
id: number;
nombre: string;
precio: number;
descripcion?: string;
}
// Partial: todas las propiedades opcionales (útil para DTOs de actualización)
type ProductoUpdate = Partial<Producto>;
// { id?: number; nombre?: string; precio?: number; descripcion?: string }
// Required: elimina la opcionalidad de todas (descripcion deja de ser opcional)
type ProductoCompleto = Required<Producto>;
// { id: number; nombre: string; precio: number; descripcion: string }
// Readonly: convierte todas en de solo lectura
type ProductoFijo = Readonly<Producto>;
// No puedes hacer productoFijo.precio = 10; // Error en compilación
Pick y Omit
// Pick: selecciona solo las propiedades que indicas
type ProductoPublico = Pick<Producto, "nombre" | "precio">;
// { nombre: string; precio: number }
// Omit: excluye las propiedades que indicas
type ProductoSinId = Omit<Producto, "id">;
// { nombre: string; precio: number; descripcion?: string }
// Caso práctico: formulario de creación sin campos autogenerados
interface Articulo {
id: number;
fecha_creacion: Date;
titulo: string;
contenido: string;
autor_id: number;
}
type FormularioNuevoArticulo = Omit<Articulo, "id" | "fecha_creacion">;
Record
// Record<K, V>: objeto con claves K y valores V
type EstadoPedido = "pendiente" | "enviado" | "entregado" | "cancelado";
type DescripcionEstado = Record<EstadoPedido, string>;
const descripciones: DescripcionEstado = {
pendiente: "Esperando confirmación",
enviado: "En camino",
entregado: "Recibido por el cliente",
cancelado: "Pedido cancelado",
};
// Record con valor complejo:
type CacheUsuarios = Record<number, { nombre: string; email: string }>;
Extract y Exclude
type T = string | number | boolean | null | undefined;
// Extract: los miembros de T que son asignables a U
type Primitivos = Extract<T, string | number>; // string | number
// Exclude: los miembros de T que NO son asignables a U
type SinNulos = Exclude<T, null | undefined>; // string | number | boolean
// Caso práctico: filtrar eventos
type TodosEventos = "click" | "focus" | "blur" | "mouseenter" | "mouseleave";
type EventoRaton = Extract<TodosEventos, `mouse${string}`>;
// "mouseenter" | "mouseleave"
NonNullable
// NonNullable<T>: elimina null y undefined de T type T1 = NonNullable<string | null | undefined>; // string type T2 = NonNullable<number | null>; // number // Equivale a: Exclude<T, null | undefined> // Muy útil en funciones que filtran nulos: const lista = [1, null, 2, undefined, 3]; const soloNumeros = lista.filter((x): x is NonNullable<typeof x> => x != null); // soloNumeros: number[]
ReturnType, Parameters e InstanceType
function crearUsuario(nombre: string, edad: number): { id: number; nombre: string } {
return { id: Math.random(), nombre };
}
type ResultadoCrearUsuario = ReturnType<typeof crearUsuario>;
// { id: number; nombre: string }
type ParamsCrearUsuario = Parameters<typeof crearUsuario>;
// [nombre: string, edad: number]
// InstanceType: el tipo de una instancia de clase
class Gestor {
conectar(): void { /* ... */ }
}
type InstanciaGestor = InstanceType<typeof Gestor>;
// Gestor (misma que usar Gestor directamente, útil con genéricos)
Awaited y NoInfer
// Awaited: desenvuelve el tipo de una Promise (recursivamente)
type A1 = Awaited<Promise<string>>; // string
type A2 = Awaited<Promise<Promise<number>>>; // number
async function obtenerDatos(): Promise<{ items: string[] }> {
return { items: [] };
}
type Datos = Awaited<ReturnType<typeof obtenerDatos>>;
// { items: string[] }
// NoInfer (TS 5.4): evita que TypeScript infiera T desde un argumento concreto
function porDefecto<T>(valor: T, defecto: NoInfer<T>): T {
return valor ?? defecto;
}
// defecto no participa en la inferencia de T, solo debe ser compatible con él
Combinar utility types
// DTO de actualización: id requerido, el resto opcional
type UpdateDTO<T extends { id: number }> =
Pick<T, "id"> & Partial<Omit<T, "id">>;
type UpdateProducto = UpdateDTO<Producto>;
// { id: number; nombre?: string; precio?: number; descripcion?: string }
// Solo las propiedades de tipo string de un objeto:
type SoloCadenasDE<T> = {
[K in keyof T as T[K] extends string ? K : never]: string;
};
Los utility types no son magia: cada uno es un mapped type o un tipo condicional definido en lib.es5.d.ts. Conocer cómo están implementados te permite combinarlos con confianza y extenderlos cuando ninguno hace exactamente lo que necesitas.
Imagen: Pexels / Myburgh Roux
