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
1. Sintaxis básica { [P in K]: T }
Section titled “1. Sintaxis básica { [P in K]: T }”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].”
2. Modificadores: readonly y ?
Section titled “2. Modificadores: readonly y ?”TypeScript permite agregar o quitar modificadores.
readonly
Section titled “readonly”// Hacer todas las propiedades readonlytype Readonly<T> = { readonly [P in keyof T]: T[P];};
// Quitar readonlytype 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 readonlyOpcional (?)
Section titled “Opcional (?)”// Hacer todas las propiedades opcionalestype 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 }Combinando modificadores
Section titled “Combinando modificadores”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.
3. Mapped types con as (TS 4.1+)
Section titled “3. Mapped types con as (TS 4.1+)”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 }Filtrar claves por tipo
Section titled “Filtrar claves por tipo”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 }Claves con prefijo
Section titled “Claves con prefijo”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:
| Utilidad | Descripció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 }Record
Section titled “Record”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 APItype 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 opcionaltype 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.