Volver al blog
Imagen de portada del artículo: TypeScript Avanzado: Generics, Utility Types y Type Guards

TypeScript Avanzado: Generics, Utility Types y Type Guards

Domina las características más potentes de TypeScript: genéricos, tipos condicionales, utility types, type guards y template literal types con ejemplos prácticos del mundo real.

16 min
German

TypeScript es mucho más que añadir tipos a JavaScript. Su sistema de tipos es tan potente que funciona como un lenguaje de programación en sí mismo. Dominar sus características avanzadas te permite escribir código más seguro, expresivo y autodocumentado.

Generics: Código reutilizable con tipos

Los genéricos permiten crear funciones, clases e interfaces que funcionan con cualquier tipo manteniendo la seguridad de tipos.

// ❌ Sin genéricos: perdemos información de tipo
function getFirst(arr: any[]): any {
  return arr[0];
}

// ✅ Con genéricos: el tipo se preserva
function getFirst<T>(arr: T[]): T {
  return arr[0];
}

const num = getFirst([1, 2, 3]);    // tipo: number 🎉
const str = getFirst(['a', 'b']);     // tipo: string 🎉

Restricciones con extends

interface HasId {
  id: string | number;
}

function findById<T extends HasId>(items: T[], id: T['id']): T | undefined {
  return items.find(item => item.id === id);
}

Utility Types: Los tipos incluidos

Partial<T> — Todas las propiedades opcionales

interface User {
  name: string;
  email: string;
  age: number;
}

function updateUser(id: string, changes: Partial<User>): void {
  // changes puede tener cualquier combinación de name, email, age
}

Pick<T, K> y Omit<T, K> — Seleccionar/excluir propiedades

type PublicUser = Omit<User, 'password'>;
type UserListItem = Pick<User, 'id' | 'name'>;
type CreateUserDto = Omit<User, 'id' | 'createdAt'>;

Record<K, V> — Diccionarios tipados

type UserRole = 'admin' | 'editor' | 'viewer';

const permissions: Record<UserRole, RolePermissions> = {
  admin:  { canEdit: true, canDelete: true, canPublish: true },
  editor: { canEdit: true, canDelete: false, canPublish: true },
  viewer: { canEdit: false, canDelete: false, canPublish: false },
};

Type Guards: Estrechamiento de tipos

interface Dog { kind: 'dog'; bark(): void; }
interface Cat { kind: 'cat'; meow(): void; }
type Animal = Dog | Cat;

function isDog(animal: Animal): animal is Dog {
  return animal.kind === 'dog';
}

function handleAnimal(animal: Animal) {
  if (isDog(animal)) {
    animal.bark(); // TypeScript sabe que es Dog aquí
  } else {
    animal.meow(); // TypeScript sabe que es Cat aquí
  }
}

Discriminated Unions

type ApiResponse<T> =
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string };

function handleResponse<T>(response: ApiResponse<T>) {
  switch (response.status) {
    case 'loading': return 'Cargando...';
    case 'success': return response.data;
    case 'error':   return response.error;
  }
}

Template Literal Types

type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${Capitalize<EventName>}`;
// tipo: 'onClick' | 'onFocus' | 'onBlur'

Conclusión

TypeScript avanzado es una inversión que se paga sola. Cada tipo bien definido es un bug que nunca llegará a producción. Empieza añadiendo genéricos a tus funciones de utilidad, usa Utility Types para no repetir interfaces, y adopta discriminated unions para manejar estados de forma segura.