Utility Types en TypeScript I: Partial, Required, Readonly y Record

Los utility types de TypeScript son transformaciones de tipos integradas en el compilador que evitan duplicar definiciones. En lugar de escribir el mismo tipo con variaciones a mano, los utility types derivan uno nuevo a partir del que ya tienes. Esta primera parte cubre los cuatro más usados en el trabajo diario: Partial, Required, Readonly y Record.

Partial: hacer opcionales todas las propiedades

Partial<T> convierte todas las propiedades de un tipo en opcionales. Es el utility type natural para los endpoints PATCH de una API, donde el cuerpo de la petición solo incluye los campos que se quieren actualizar:

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

type ActualizacionUsuario = Partial;
// { id?: number; nombre?: string; email?: string; rol?: "admin" | "usuario" }

async function actualizarUsuario(
  id: number,
  cambios: Partial
): Promise {
  return fetch(`/api/usuarios/${id}`, {
    method: "PATCH",
    body: JSON.stringify(cambios),
  }).then(r => r.json());
}

// Solo se envían los campos modificados
actualizarUsuario(1, { email: "[email protected]" });

Required: forzar que todas las propiedades estén presentes

Required<T> es el opuesto de Partial: elimina el ? de todas las propiedades. Es útil cuando tienes un tipo con propiedades opcionales en la configuración de entrada pero necesitas garantizar que todas están presentes después de aplicar los valores por defecto:

interface ConfiguracionOpcional {
  timeout?: number;
  reintentos?: number;
  baseUrl?: string;
}

const DEFAULTS: Required = {
  timeout: 5000,
  reintentos: 3,
  baseUrl: "https://api.ejemplo.com",
};

function resolver(config?: ConfiguracionOpcional): Required {
  return { ...DEFAULTS, ...config };
}

Readonly: propiedades de solo lectura

Readonly<T> marca todas las propiedades como readonly. TypeScript dará error si intentas reasignar cualquiera de ellas. Es ideal para configuración inmutable o para el estado en arquitecturas que prohíben la mutación directa:

interface Config {
  apiKey: string;
  endpoint: string;
  debug: boolean;
}

const CONFIG: Readonly = {
  apiKey: process.env.API_KEY ?? "",
  endpoint: "https://api.ejemplo.com",
  debug: false,
};

CONFIG.debug = true; // Error: no se puede asignar a 'debug' porque es de solo lectura

Nota: Readonly es superficial. Si Config tuviera una propiedad con un tipo objeto, ese objeto interno no sería readonly. Para una versión profunda necesitarías un tipo recursivo personalizado.

Record: diccionarios tipados con claves controladas

Record<K, V> crea un tipo de objeto donde las claves son de tipo K y los valores de tipo V. Es más expresivo que { [key: string]: V } porque K puede ser un union type, lo que restringe las claves válidas:

type CodigoHTTP = 200 | 201 | 400 | 401 | 403 | 404 | 500;

const mensajesHTTP: Record = {
  200: "OK",
  201: "Created",
  400: "Bad Request",
  401: "Unauthorized",
  403: "Forbidden",
  404: "Not Found",
  500: "Internal Server Error",
};

// TypeScript verifica que están todos los códigos del union
// Si falta alguno, error de compilación

Otro uso habitual es agrupar datos por categoría:

type Categoria = "frontend" | "backend" | "devops";

interface Articulo {
  titulo: string;
  url: string;
}

const articulosPorCategoria: Record = {
  frontend: [],
  backend: [],
  devops: [],
};

Cuando las claves son strings arbitrarios que no conoces en tiempo de compilación, usa Record<string, V> o la sintaxis de índice { [key: string]: V }. Cuando las claves son un conjunto cerrado y conocido, un union type como K hace que TypeScript compruebe que el objeto cubre todas las posibilidades.

Imagen: Pexels / Godfrey Atima

COMPARTE ESTE ARTÍCULO

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