Utility types de TypeScript: Omit, Pick, Extract, Exclude, ReturnType, Parameters y más

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

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP