Keyof, typeof y mapped types en TypeScript

Avanzado
TypeScript
TypeScript
Actualizado: 04/05/2026

Diagrama: tutorial-typescript-keyof-mapped-types

El operador keyof

El operador keyof toma un tipo de objeto y produce una union de literales de cadena (o numéricos) que representan sus claves:

type Usuario = {
    nombre: string
    edad: number
    email: string
}

type ClavesUsuario = keyof Usuario
// "nombre" | "edad" | "email"

Este tipo union restringe que solo se puedan usar claves válidas del objeto original:

function obtenerPropiedad(usuario: Usuario, clave: keyof Usuario) {
    return usuario[clave]
}

const usuario: Usuario = { nombre: "Ana", edad: 28, email: "ana@ejemplo.com" }

obtenerPropiedad(usuario, "nombre")
// obtenerPropiedad(usuario, "telefono") // Error: "telefono" no es clave de Usuario

Keyof con genéricos

La combinación de keyof con genéricos permite crear funciones que preservan el tipo de retorno según la clave accedida:

function obtenerValor<T, K extends keyof T>(objeto: T, clave: K): T[K] {
    return objeto[clave]
}

const config = {
    tema: "oscuro",
    idioma: "es",
    notificaciones: true
}

const tema = obtenerValor(config, "tema")
// tipo: string

const notificaciones = obtenerValor(config, "notificaciones")
// tipo: boolean

La restricción K extends keyof T garantiza que K solo pueda ser una clave válida de T. El tipo de retorno T[K] es un tipo de acceso indexado que representa el tipo de la propiedad seleccionada.

Función de actualización segura

function actualizarPropiedad<T, K extends keyof T>(
    objeto: T,
    clave: K,
    valor: T[K]
): T {
    return { ...objeto, [clave]: valor }
}

const usuario = { nombre: "Ana", edad: 28, activo: true }

const actualizado = actualizarPropiedad(usuario, "edad", 29)
// actualizarPropiedad(usuario, "edad", "treinta") // Error: string no es number

Keyof con index signatures

Cuando un tipo tiene una index signature, keyof devuelve el tipo del índice:

type Diccionario = {
    [clave: string]: number
}

type ClaveDiccionario = keyof Diccionario
// string | number (porque obj[0] equivale a obj["0"] en JavaScript)

type ArrayLike = {
    [indice: number]: string
}

type ClaveArray = keyof ArrayLike
// number

Tipos de acceso indexado T[K]

Los tipos de acceso indexado permiten obtener el tipo de una propiedad específica de otro tipo usando la sintaxis de corchetes:

type Persona = {
    nombre: string
    direccion: {
        calle: string
        ciudad: string
        codigoPostal: number
    }
    hobbies: string[]
}

type TipoDireccion = Persona["direccion"]
// { calle: string; ciudad: string; codigoPostal: number }

type TipoHobbies = Persona["hobbies"]
// string[]

type TipoCiudad = Persona["direccion"]["ciudad"]
// string

Se pueden usar uniones como índice para obtener la union de los tipos correspondientes:

type NombreOEdad = Persona["nombre" | "hobbies"]
// string | string[]

La combinación con keyof obtiene la union de todos los tipos de valor de un objeto:

type ValoresPersona = Persona[keyof Persona]
// string | { calle: string; ciudad: string; codigoPostal: number } | string[]

Acceso indexado con number

Para obtener el tipo de los elementos de un array, se usa number como índice:

const COLORES = ["rojo", "verde", "azul"] as const

type Color = typeof COLORES[number]
// "rojo" | "verde" | "azul"

El índice en un tipo de acceso indexado debe ser un tipo, no un valor. No se puede usar una variable const como índice directamente, pero si un type alias.

El operador typeof en posición de tipo

TypeScript añade un operador typeof que funciona en posiciones de tipo para extraer el tipo de un valor:

const configuracion = {
    host: "localhost",
    puerto: 5432,
    tls: false
}

type TipoConfig = typeof configuracion
// { host: string; puerto: number; tls: boolean }

Esto es especialmente útil cuando el valor es la fuente de verdad y se quiere derivar un tipo a partir de el, en lugar de definir un tipo primero:

function crearConexion(host: string, puerto: number) {
    return { host, puerto, conectado: false }
}

type Conexion = ReturnType<typeof crearConexion>
// { host: string; puerto: number; conectado: boolean }

Combinación de keyof y typeof

La combinación keyof typeof es un patrón frecuente para extraer las claves de un objeto en tiempo de ejecución como tipo:

const CODIGOS_HTTP = {
    OK: 200,
    CREADO: 201,
    NO_ENCONTRADO: 404,
    ERROR_SERVIDOR: 500
}

type NombreCodigo = keyof typeof CODIGOS_HTTP
// "OK" | "CREADO" | "NO_ENCONTRADO" | "ERROR_SERVIDOR"

type ValorCodigo = typeof CODIGOS_HTTP[keyof typeof CODIGOS_HTTP]
// number (200 | 201 | 404 | 500 si se usa as const)

Con as const se obtienen los valores literales:

const CODIGOS = {
    OK: 200,
    CREADO: 201,
    NO_ENCONTRADO: 404,
    ERROR_SERVIDOR: 500
} as const

type CodigoHTTP = typeof CODIGOS[keyof typeof CODIGOS]
// 200 | 201 | 404 | 500

typeof con funciones

typeof permite extraer el tipo de una función para usarlo con tipos utilitarios:

function procesar(datos: string[], opciones: { paralelo: boolean }) {
    return { resultado: datos.length, exitoso: true }
}

type ParamsProcesar = Parameters<typeof procesar>
// [datos: string[], opciones: { paralelo: boolean }]

type ResultadoProcesar = ReturnType<typeof procesar>
// { resultado: number; exitoso: boolean }

Mapped types

Los mapped types crean nuevos tipos transformando sistemáticamente las propiedades de un tipo existente. La sintaxis usa in para iterar sobre un conjunto de claves:

type Opcional<T> = {
    [K in keyof T]?: T[K]
}

interface Usuario {
    nombre: string
    edad: number
    activo: boolean
}

type UsuarioOpcional = Opcional<Usuario>
// { nombre?: string; edad?: number; activo?: boolean }

Modificadores readonly y optional

Los mapped types soportan los modificadores readonly y ?, y permiten anadirlos o eliminarlos con los prefijos + y -:

type SoloLectura<T> = {
    readonly [K in keyof T]: T[K]
}

type Mutable<T> = {
    -readonly [K in keyof T]: T[K]
}

type Requerido<T> = {
    [K in keyof T]-?: T[K]
}

El prefijo - elimina el modificador existente. Sin prefijo, se asume +:

interface ConfigInmutable {
    readonly host: string
    readonly puerto: number
}

type ConfigMutable = Mutable<ConfigInmutable>
// { host: string; puerto: number } (sin readonly)

interface DatosOpcionales {
    nombre?: string
    email?: string
}

type DatosRequeridos = Requerido<DatosOpcionales>
// { nombre: string; email: string } (sin ?)

Transformación de tipos de valor

Los mapped types pueden cambiar el tipo de cada propiedad usando tipos condicionales:

type StringificarNumeros<T> = {
    [K in keyof T]: T[K] extends number ? string : T[K]
}

interface Producto {
    id: number
    nombre: string
    precio: number
    disponible: boolean
}

type ProductoDTO = StringificarNumeros<Producto>
// { id: string; nombre: string; precio: string; disponible: boolean }

Un patrón común es hacer que todas las propiedades acepten null:

type Nullable<T> = {
    [K in keyof T]: T[K] | null
}

type ProductoNullable = Nullable<Producto>
// { id: number | null; nombre: string | null; precio: number | null; disponible: boolean | null }

Remapeo de claves con as

A partir de TypeScript 4.1, la cláusula as permite transformar las claves durante la iteración:

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}

type GettersUsuario = Getters<Usuario>
// {
//     getNombre: () => string
//     getEdad: () => number
//     getActivo: () => boolean
// }

Filtrado de propiedades con never

Producir never como clave en un remapeo excluye esa propiedad del tipo resultante:

type SoloStrings<T> = {
    [K in keyof T as T[K] extends string ? K : never]: T[K]
}

type StringsUsuario = SoloStrings<Usuario>
// { nombre: string }

type ExcluirPropiedades<T, U> = {
    [K in keyof T as T[K] extends U ? never : K]: T[K]
}

type SinBooleans = ExcluirPropiedades<Usuario, boolean>
// { nombre: string; edad: number }

Mapped types sobre uniones arbitrarias

Los mapped types no están limitados a claves de objetos. Se puede iterar sobre cualquier union:

type EventosPermitidos = "click" | "hover" | "submit"

type ManejadoresEvento = {
    [K in EventosPermitidos as `on${Capitalize<K>}`]: (evento: Event) => void
}
// {
//     onClick: (evento: Event) => void
//     onHover: (evento: Event) => void
//     onSubmit: (evento: Event) => void
// }

Patrones avanzados de programación a nivel de tipos

Pick y Omit manuales

Los utility types Pick y Omit se pueden implementar con mapped types:

type MiPick<T, K extends keyof T> = {
    [P in K]: T[P]
}

type MiOmit<T, K extends keyof T> = {
    [P in keyof T as P extends K ? never : P]: T[P]
}

type InfoBasica = MiPick<Usuario, "nombre" | "email">
// { nombre: string; email: string } -- asumiendo email en Usuario

type SinEdad = MiOmit<Usuario, "edad">
// { nombre: string; activo: boolean }

Record tipado

El utility type Record también es un mapped type:

type MiRecord<K extends string | number | symbol, V> = {
    [P in K]: V
}

type TablaPermisos = MiRecord<"leer" | "escribir" | "eliminar", boolean>
// { leer: boolean; escribir: boolean; eliminar: boolean }

Tipo DeepReadonly recursivo

Los mapped types permiten transformaciones recursivas:

type DeepReadonly<T> = {
    readonly [K in keyof T]: T[K] extends object
        ? T[K] extends Function
            ? T[K]
            : DeepReadonly<T[K]>
        : T[K]
}

interface Estado {
    usuario: {
        nombre: string
        preferencias: {
            tema: string
            idioma: string
        }
    }
    contador: number
}

type EstadoInmutable = DeepReadonly<Estado>
// Todas las propiedades, incluyendo las anidadas, son readonly

Tipo para formularios

Un ejemplo práctico que combina keyof, mapped types y tipos condicionales:

interface DatosFormulario {
    nombre: string
    email: string
    edad: number
    acepta: boolean
}

type ErroresFormulario<T> = {
    [K in keyof T]?: string
}

type EstadoCampos<T> = {
    [K in keyof T]: {
        valor: T[K]
        tocado: boolean
        error?: string
    }
}

type CamposFormulario = EstadoCampos<DatosFormulario>
// {
//     nombre: { valor: string; tocado: boolean; error?: string }
//     email: { valor: string; tocado: boolean; error?: string }
//     edad: { valor: number; tocado: boolean; error?: string }
//     acepta: { valor: boolean; tocado: boolean; error?: string }
// }

function validarCampo<T, K extends keyof T>(
    campos: EstadoCampos<T>,
    clave: K
): string | undefined {
    const campo = campos[clave]
    if (!campo.tocado) return undefined
    if (campo.valor === "" || campo.valor === null) {
        return `El campo ${String(clave)} es requerido`
    }
    return undefined
}

Lookup types para API tipada

Combinando todos los operadores, se puede construir un sistema de tipos que modele una API completa:

type Endpoints = {
    "/usuarios": {
        GET: { respuesta: { id: string; nombre: string }[] }
        POST: { cuerpo: { nombre: string; email: string }; respuesta: { id: string } }
    }
    "/productos": {
        GET: { respuesta: { id: string; titulo: string; precio: number }[] }
    }
}

type MetodosDeRuta<R extends keyof Endpoints> = keyof Endpoints[R]

type Respuesta<
    R extends keyof Endpoints,
    M extends keyof Endpoints[R]
> = Endpoints[R][M] extends { respuesta: infer Resp } ? Resp : never

type RespUsuariosGET = Respuesta<"/usuarios", "GET">
// { id: string; nombre: string }[]

type RespUsuariosPOST = Respuesta<"/usuarios", "POST">
// { id: string }

La combinación de keyof, tipos de acceso indexado, typeof y mapped types forma el nucleo de la metaprogramación de tipos en TypeScript, permitiendo crear abstracciones de tipo que se adaptan automáticamente a los cambios en las estructuras de datos subyacentes.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en TypeScript

Documentación oficial de TypeScript
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, TypeScript es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de TypeScript

Explora más contenido relacionado con TypeScript y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Dominar el operador keyof para extraer claves como tipos union, los tipos de acceso indexado T[K], el operador typeof para extraer tipos de valores, la combinación de keyof con typeof, los mapped types con modificadores y remapeo de claves, y patrones de programación a nivel de tipos.