Skip to content

Lección 90 — Constraints en Genéricos y Utility Types

Los constraints (restricciones) limitan qué tipos puede aceptar un genérico. Los utility types son tipos predefinidos que transforman otros tipos de forma muy útil.

Puedes restringir un genérico para que solo acepte tipos que cumplan cierta condición:

function longitud<T extends { length: number }>(item: T): number {
return item.length;
}
longitud('Hola'); // ✅ string tiene length
longitud([1, 2, 3]); // ✅ array tiene length
longitud(42); // ❌ number no tiene length

keyof obtiene una unión de las claves de un tipo:

function obtenerPropiedad<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const usuario = { nombre: 'Ana', edad: 30 };
obtenerPropiedad(usuario, 'nombre'); // ✅ tipo: string
obtenerPropiedad(usuario, 'email'); // ❌ Error

TypeScript incluye una serie de utility types globales que transforman tipos:

interface Usuario {
id: number;
nombre: string;
email: string;
activo: boolean;
}

Todas las propiedades se vuelven opcionales:

type UsuarioParcial = Partial<Usuario>;
// { id?: number; nombre?: string; email?: string; activo?: boolean }

Todas las propiedades se vuelven requeridas:

type UsuarioRequerido = Required<Partial<Usuario>>;

Selecciona solo las propiedades indicadas:

type UsuarioBasico = Pick<Usuario, 'id' | 'nombre'>;
// { id: number; nombre: string }

Omite las propiedades indicadas:

type UsuarioSinEmail = Omit<Usuario, 'email'>;
// { id: number; nombre: string; activo: boolean }

Crea un tipo con claves K y valores V:

type Pagina = Record<'inicio' | 'perfil' | 'admin', string>;
// { inicio: string; perfil: string; admin: string }
type T1 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T2 = Extract<'a' | 'b' | 'c', 'a' | 'c'>; // 'a' | 'c'

Elimina null y undefined:

type T3 = NonNullable<string | number | null | undefined>; // string | number

Obtiene el tipo de retorno de una función:

function crearUsuario() { return { id: 1, nombre: 'Ana' }; }
type UsuarioType = ReturnType<typeof crearUsuario>;
// { id: number; nombre: string }
// Constraint + keyof
function obtenerValores<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(key => obj[key]);
}
const user = { id: 1, nombre: 'Ana', edad: 30 };
const valores = obtenerValores(user, ['nombre', 'edad']); // tipo: (string | number)[]
// Utility types en acción
interface Producto {
id: number;
nombre: string;
precio: number;
descripcion?: string;
categoria: string;
}
// Actualización parcial
function actualizarProducto(id: number, cambios: Partial<Producto>): void {
// cambios puede tener cualquier subconjunto de propiedades
}
actualizarProducto(1, { precio: 29.99 });
actualizarProducto(2, { nombre: 'Nuevo nombre', descripcion: '...' });
// Mapa de configuraciones
type ConfigApp = Record<string, string | number | boolean>;
const config: ConfigApp = {
url: 'https://api.example.com',
puerto: 443,
debug: true
};
// Pick para DTOs
type ProductoLista = Pick<Producto, 'id' | 'nombre' | 'precio'>;
type ProductoDetalle = Omit<Producto, 'categoria'>;

Define una interfaz Tarea con: id, titulo, descripcion, completada, fechaLimite (opcional), etiquetas (array de strings). Luego crea:

  1. Un type TareaCrear usando Omit para excluir id.
  2. Un type TareaResumen usando Pick para solo id, titulo, completada.
  3. Un Record<string, Tarea> para un gestor de tareas por proyecto.
  4. Una función genérica actualizarTarea(id: number, cambios: Partial<Tarea>): void.

Los utility types no mutan el tipo original. Crean un nuevo tipo derivado. El tipo base permanece intacto para otros usos.

Domina estos utility types: Partial, Pick, Omit y Record son los que más usarás. Ahorran toneladas de código repetitivo y hacen tus tipos más expresivos y mantenibles.