Skip to content

Lección 93: Mapped Types

🎯 Objetivo: Aprender a crear tipos iterando sobre claves con mapped types, y usar las utilidades de modificación (readonly, ?, as).


🧠 Concepto

Un mapped type itera sobre una unión de claves (K) y crea un tipo donde cada clave P se asigna al tipo T.

💻 Ejemplo

type Mutable<T> = {
[P in keyof T]: T[P];
};
type Opciones<T> = {
[P in keyof T]: boolean;
};
type Config = {
url: string;
timeout: number;
};
type OpcionesConfig = Opciones<Config>;
// { url: boolean; timeout: boolean }

📝 Lectura: “Para cada propiedad P en las claves de T, crea una propiedad con el tipo de T[P].”


TypeScript permite agregar o quitar modificadores.

// Hacer todas las propiedades readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Quitar readonly
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type Config = {
url: string;
readonly timeout: number;
};
type ConfigMutable = Mutable<Config>;
// { url: string; timeout: number } — timeout ya no es readonly
// Hacer todas las propiedades opcionales
type Partial<T> = {
[P in keyof T]?: T[P];
};
// Quitar opcional (todas requeridas)
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Usuario = {
nombre?: string;
edad?: number;
email: string;
};
type UsuarioRequerido = Required<Usuario>;
// { nombre: string; edad: number; email: string }
type Config = {
url: string;
timeout: number;
retries?: number;
};
type ConfigProduccion = {
+readonly [P in keyof Config]-?: Config[P];
};
// { readonly url: string; readonly timeout: number; readonly retries: number }
// Todo requerido y readonly

⚠️ El + es implícito: readonly y ? agregan; -readonly y -? eliminan.


La cláusula as permite remapear (renombrar) las claves.

type Getter<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
type Persona = { nombre: string; edad: number };
type Getters = Getter<Persona>;
// { getNombre: () => string; getEdad: () => number }
type Funciones<T> = {
[P in keyof T as T[P] extends Function ? P : never]: T[P];
};
type Servicio = {
iniciar: () => void;
detener: () => void;
version: string;
timeout: number;
};
type SoloMetodos = Funciones<Servicio>;
// { iniciar: () => void; detener: () => void }
type Eventos = {
click: {};
hover: {};
focus: {};
};
type PrefijoEventos = {
[P in keyof Eventos as `on${Capitalize<string & P>}`]: Eventos[P];
};
// { onClick: {}; onHover: {}; onFocus: {} }

4. Utilidades nativas basadas en mapped types

Section titled “4. Utilidades nativas basadas en mapped types”

TypeScript incluye varias utilidades que son mapped types:

UtilidadDescripción
Partial<T>Todas las propiedades opcionales
Required<T>Todas las propiedades requeridas
Readonly<T>Todas las propiedades readonly
Pick<T, K>Selecciona un subconjunto de claves
Record<K, V>Tipo con claves K y valores V
Omit<T, K>Excluye claves
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Persona = { nombre: string; edad: number; email: string };
type SoloNombre = Pick<Persona, "nombre" | "email">;
// { nombre: string; email: string }
type Record<K extends keyof any, V> = {
[P in K]: V;
};
type Roles = Record<"admin" | "user" | "guest", string[]>;
// { admin: string[]; user: string[]; guest: string[] }
const permisos: Roles = {
admin: ["crear", "editar", "eliminar"],
user: ["ver"],
guest: [],
};

5. Ejemplo completo: sistema de transformación de tipos

Section titled “5. Ejemplo completo: sistema de transformación de tipos”
// Definimos un tipo de respuesta API
type RespuestaAPI = {
id: number;
nombre: string;
email: string;
creadoEn: Date;
actualizadoEn: Date;
};
// Creamos un tipo para respuestas públicas (sin fechas ni email)
type RespuestaPublica<T> = {
[P in keyof T as P extends "email" | "creadoEn" | "actualizadoEn"
? never
: P]: T[P];
};
type Publico = RespuestaPublica<RespuestaAPI>;
// { id: number; nombre: string }
// Versión con sufijo opcional
type VersionFormulario<T> = {
[P in keyof T as `${string & P}Field`]?: string;
};
type FormularioPersona = VersionFormulario<RespuestaAPI>;
// { idField?: string; nombreField?: string; emailField?: string; ... }
// DeepPartial (recursivo)
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Function
? T[P]
: DeepPartial<T[P]>
: T[P];
};

📝 Resumen: Los mapped types permiten transformar tipos existentes iterando sobre sus claves. Con los modificadores readonly/? y la cláusula as puedes crear tipos derivados complejos sin perder safety. Son la base de todas las utilidades de tipos de TypeScript.