← React & TypeScript

Fundamentos de TypeScript

¿Qué utility types de TypeScript conoces y para qué sirven?

Ver respuesta — intenta responderla en voz alta primero

Son tipos genéricos incluidos en TypeScript que transforman tipos existentes en lugar de duplicarlos: Partial hace todo opcional, Pick y Omit seleccionan o quitan propiedades, Record construye objetos clave-valor y ReturnType extrae el tipo de retorno de una función.

La idea central: define tu tipo base una vez y deriva las variantes. Si mañana el tipo base cambia, todas las variantes se actualizan solas.

Los más usados:

  • Partial<T>: todas las propiedades pasan a ser opcionales. Perfecto para funciones de actualización donde solo mandas los campos que cambian. Required<T> es lo contrario: todas obligatorias.
  • Pick<T, K>: nuevo tipo solo con las propiedades que elijas.
  • Omit<T, K>: nuevo tipo sin las propiedades que indiques. Típico: quitar id y creadoEn para el formulario de creación.
  • Record<K, V>: objeto cuyas claves son K y cuyos valores son V. Muy útil con uniones de literales como claves.
  • ReturnType<F>: extrae el tipo que devuelve una función, sin escribirlo a mano.

La señal para usarlos: cuando estés a punto de copiar y pegar un tipo cambiándole dos cosas.

interface Usuario {
  id: number;
  nombre: string;
  email: string;
  rol: "admin" | "usuario";
}

// Partial: actualizar solo algunos campos
function actualizarUsuario(id: number, cambios: Partial<Usuario>): void {
  console.log(`Actualizando ${id}`, cambios);
}
actualizarUsuario(1, { nombre: "Ana María" }); // no exige el resto

// Pick: solo lo necesario para una tarjeta de perfil (nombre + email)
type TarjetaPerfil = Pick<Usuario, "nombre" | "email">;

// Omit: el formulario de alta no tiene id todavía
type NuevoUsuario = Omit<Usuario, "id">;
const alta: NuevoUsuario = { nombre: "Luis", email: "luis@mail.com", rol: "usuario" };

// Record: un diccionario tipado de permisos por rol
type Permisos = Record<Usuario["rol"], string[]>;
// Si falta un rol o sobra una clave, no compila
const permisos: Permisos = { admin: ["leer", "borrar"], usuario: ["leer"] };

// ReturnType: reutilizar el tipo que devuelve una función
function crearSesion(usuario: Usuario) {
  return { token: "abc123", usuario, expira: Date.now() + 3600 };
}
// Sesion = { token: string; usuario: Usuario; expira: number }
type Sesion = ReturnType<typeof crearSesion>;

Duplicar el tipo a mano en lugar de derivarlo:

// (usa la interface Usuario del ejemplo anterior)
// Duplicado: si Usuario cambia, esto queda desincronizado en silencio
interface UsuarioEditableMal {
  nombre?: string;
  email?: string;
  rol?: "admin" | "usuario";
}

// Derivado: se actualiza solo cuando Usuario cambia
type UsuarioEditable = Partial<Omit<Usuario, "id">>;

El problema del duplicado no es escribirlo hoy, es mantenerlo mañana: alguien añade un campo a Usuario y la copia manual no se entera.

"Los utility types son transformadores de tipos que trae TypeScript para no duplicar definiciones. Los que más uso: Partial para funciones de actualización donde solo mando los campos que cambian, Omit para variantes como 'usuario sin id' en formularios de creación, Pick cuando un componente solo necesita dos o tres propiedades, Record para diccionarios tipados y ReturnType para extraer lo que devuelve una función. La ventaja es el mantenimiento: defino el tipo base una vez y las variantes se actualizan solas cuando cambia."

Reto rápido

Con la interfaz Usuario del ejemplo: 1) define VistaPublica con solo nombre y rol; 2) define BorradorUsuario donde todo es opcional excepto que no incluye id; 3) define UsuariosPorEmail, un diccionario de string a Usuario.

Ver solución
interface Usuario {
  id: number;
  nombre: string;
  email: string;
  rol: "admin" | "usuario";
}

// 1) Solo dos propiedades
type VistaPublica = Pick<Usuario, "nombre" | "rol">;

// 2) Sin id y todo opcional: se pueden combinar utility types
type BorradorUsuario = Partial<Omit<Usuario, "id">>;

// 3) Diccionario clave string -> Usuario
type UsuariosPorEmail = Record<string, Usuario>;

const vista: VistaPublica = { nombre: "Ana", rol: "admin" };
const borrador: BorradorUsuario = { nombre: "Luis" };