Keyof y Mapped Types

Intermedio
TypeScript
TypeScript
Actualizado: 28/08/2025

Operador keyof

El operador keyof es una herramienta fundamental en TypeScript que permite extraer las claves de un tipo de objeto como un tipo de unión literal. Su funcionamiento es directo: toma un tipo de objeto y produce un tipo que representa todas sus propiedades como literales de cadena.

La sintaxis básica utiliza la palabra clave keyof seguida del tipo del cual queremos extraer las claves:

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

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

Uso con interfaces

El operador keyof funciona perfectamente con interfaces, manteniendo la misma sintaxis:

interface Producto {
  id: number;
  titulo: string;
  precio: number;
  disponible: boolean;
}

type PropiedadesProducto = keyof Producto; // "id" | "titulo" | "precio" | "disponible"

// Podemos usar este tipo para crear funciones que solo acepten claves válidas
function obtenerPropiedad(producto: Producto, clave: PropiedadesProducto) {
  return producto[clave];
}

const producto: Producto = {
  id: 1,
  titulo: "Laptop",
  precio: 899,
  disponible: true
};

const titulo = obtenerPropiedad(producto, "titulo"); // ✅ Válido
// const invalido = obtenerPropiedad(producto, "stock"); // ❌ Error: no existe la propiedad

Combinación con generics

Una de las aplicaciones más útiles del operador keyof es su combinación con tipos genéricos para crear funciones flexibles y seguras:

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

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

const tema = obtenerValor(configuracion, "tema"); // tipo: string
const idioma = obtenerValor(configuracion, "idioma"); // tipo: string
const notificaciones = obtenerValor(configuracion, "notificaciones"); // tipo: boolean

En este ejemplo, K extends keyof T garantiza que K solo puede ser una clave válida de T, mientras que el tipo de retorno T[K] representa exactamente el tipo de la propiedad seleccionada.

Trabajar con objetos dinámicos

El operador keyof resulta especialmente valioso cuando trabajamos con objetos cuya estructura puede variar:

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 usuarioActualizado = actualizarPropiedad(usuario, "edad", 29);
// const error = actualizarPropiedad(usuario, "edad", "treinta"); // ❌ Error: tipo incorrecto

Keyof con tipos primitivos

Cuando aplicamos keyof a tipos primitivos, obtenemos las claves de sus prototipos correspondientes:

type ClavesString = keyof string; // Incluye métodos como "charAt", "slice", "toLowerCase", etc.
type ClavesNumber = keyof number; // Incluye métodos como "toString", "toFixed", "valueOf", etc.
type ClavesArray = keyof Array<any>; // Incluye métodos como "push", "pop", "map", "filter", etc.

Sin embargo, en la práctica, es más común usar keyof con tipos de objeto definidos por el desarrollador.

Casos de uso prácticos

El operador keyof es fundamental para crear sistemas de validación y manipulación de datos seguros:

interface FormularioContacto {
  nombre: string;
  email: string;
  mensaje: string;
}

type CamposFormulario = keyof FormularioContacto;

class ValidadorFormulario {
  private errores: Partial<Record<CamposFormulario, string>> = {};

  validarCampo(campo: CamposFormulario, valor: string): boolean {
    if (!valor.trim()) {
      this.errores[campo] = `El campo ${campo} es requerido`;
      return false;
    }
    
    if (campo === "email" && !this.esEmailValido(valor)) {
      this.errores[campo] = "Email inválido";
      return false;
    }

    delete this.errores[campo];
    return true;
  }

  private esEmailValido(email: string): boolean {
    return email.includes("@");
  }
}

Esta aproximación garantiza que solo podamos validar campos que realmente existen en nuestra interfaz, evitando errores tipográficos y mejorando la mantenibilidad del código.

Mapped types básicos

Los mapped types permiten crear nuevos tipos transformando sistemáticamente las propiedades de un tipo existente. Esta característica utiliza la sintaxis de indexación para iterar sobre las claves de un tipo y aplicar transformaciones específicas a cada propiedad.

La sintaxis básica de un mapped type sigue este patrón:

type NuevoTipo<T> = {
  [K in keyof T]: TipoTransformado
}

Transformación de propiedades

El caso más simple consiste en transformar el tipo de todas las propiedades de un objeto:

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

// Convertir todas las propiedades a string
type UsuarioComoString<T> = {
  [K in keyof T]: string;
}

type UsuarioStringificado = UsuarioComoString<Usuario>;
// Resultado: { nombre: string; edad: string; activo: string; }

Propiedades opcionales

Podemos hacer que todas las propiedades sean opcionales usando el modificador ?:

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

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

// Ejemplo práctico para actualizaciones parciales
function actualizarUsuario(usuario: Usuario, cambios: UsuarioOpcional): Usuario {
  return {
    ...usuario,
    ...cambios
  };
}

const usuario: Usuario = { nombre: "Carlos", edad: 30, activo: true };
const usuarioActualizado = actualizarUsuario(usuario, { edad: 31 });

Propiedades de solo lectura

El modificador readonly se aplica de manera similar para crear versiones inmutables:

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

type UsuarioInmutable = SoloLectura<Usuario>;
// Resultado: { readonly nombre: string; readonly edad: number; readonly activo: boolean; }

const usuarioFijo: UsuarioInmutable = { nombre: "Elena", edad: 25, activo: true };
// usuarioFijo.nombre = "Ana"; // ❌ Error: no se puede asignar a una propiedad readonly

Preservación de tipos originales

Los mapped types pueden preservar los tipos originales mientras aplican modificadores:

interface Configuracion {
  tema: "claro" | "oscuro";
  idioma: "es" | "en" | "fr";
  notificaciones: boolean;
  maxIntentos: number;
}

type ConfiguracionOpcional = {
  [K in keyof Configuracion]?: Configuracion[K];
}

// Los tipos literales se mantienen intactos
const configParcial: ConfiguracionOpcional = {
  tema: "oscuro", // ✅ Solo acepta "claro" | "oscuro"
  notificaciones: true
  // idioma y maxIntentos son opcionales
};

Transformación condicional básica

Podemos aplicar transformaciones condicionales simples usando tipos condicionales:

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

interface Datos {
  id: number;
  nombre: string;
  precio: number;
  disponible: boolean;
}

type DatosStringificados = StringificarNumeros<Datos>;
// Resultado: { id: string; nombre: string; precio: string; disponible: boolean; }

Combinar con keyof para crear utilidades

Los mapped types se combinan naturalmente con keyof para crear herramientas de manipulación de tipos:

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

interface Producto {
  nombre: string;
  categoria: string;
  precio: number;
}

type ProductoConDefectos = CamposPorDefecto<Producto>;
// Resultado: { nombre: string | null; categoria: string | null; precio: number | null; }

// Función que inicializa con valores por defecto
function crearProductoVacio(): ProductoConDefectos {
  return {
    nombre: null,
    categoria: null,
    precio: null
  };
}

Renombrado de propiedades

Los mapped types permiten renombrar propiedades usando template literal types:

type ConPrefijo<T, P extends string> = {
  [K in keyof T as `${P}${string & K}`]: T[K];
}

interface Medidas {
  ancho: number;
  alto: number;
}

type MedidasEnPixeles = ConPrefijo<Medidas, "px">;
// Resultado: { pxancho: number; pxalto: number; }

Casos prácticos de uso

Los mapped types resultan especialmente útiles para crear APIs flexibles:

interface EstadoFormulario {
  nombre: string;
  email: string;
  edad: number;
}

// Tipo para manejar errores de validación
type ErroresValidacion<T> = {
  [K in keyof T]?: string;
}

// Tipo para manejar estado de carga
type EstadoCarga<T> = {
  [K in keyof T]: boolean;
}

class ManejadorFormulario {
  private valores: EstadoFormulario = { nombre: "", email: "", edad: 0 };
  private errores: ErroresValidacion<EstadoFormulario> = {};
  private cargando: EstadoCarga<EstadoFormulario> = {
    nombre: false,
    email: false,
    edad: false
  };

  establecerError(campo: keyof EstadoFormulario, mensaje: string): void {
    this.errores[campo] = mensaje;
  }

  establecerCarga(campo: keyof EstadoFormulario, estado: boolean): void {
    this.cargando[campo] = estado;
  }
}

Esta aproximación garantiza que los tipos de errores y estados de carga coincidan exactamente con los campos del formulario, manteniendo la coherencia del sistema de tipos incluso cuando la estructura del formulario evoluciona.

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

  • Comprender el funcionamiento del operador keyof para extraer claves de tipos de objetos.
  • Aplicar keyof con interfaces y tipos genéricos para funciones seguras.
  • Crear mapped types para transformar propiedades de tipos existentes.
  • Utilizar modificadores como readonly y opcional en mapped types.
  • Combinar keyof y mapped types para construir utilidades y APIs flexibles.