keyof, typeof y as const en profundidad
keyof, typeof y as const son tres operadores que conectan el sistema de tipos con el mundo en runtime. Combinados, permiten construir tipos ultra-precisos derivados directamente de tus valores.
keyof T — union de claves de un tipo
keyof T produce una union de todos los nombres de propiedad de un tipo. Es la base para escribir funciones que aceptan solo claves válidas de un objeto.
interface Producto {
id: number;
nombre: string;
precio: number;
stock: number;
}
// keyof Producto = "id" | "nombre" | "precio" | "stock"
type ClavesProducto = keyof Producto;
// Función que solo acepta claves válidas del tipo T
function obtener<T, K extends keyof T>(obj: T, clave: K): T[K] {
return obj[clave];
}
const prod: Producto = { id: 1, nombre: "Monitor", precio: 299, stock: 10 };
const nombre = obtener(prod, "nombre"); // string ← TypeScript infiere T[K]
const precio = obtener(prod, "precio"); // number
// obtener(prod, "descripcion"); // ❌ Error: "descripcion" no existe
console.log(nombre); // "Monitor"
console.log(precio); // 299
// Ordenar un array por cualquier clave numérica
function ordenarPor<T>(arr: T[], clave: keyof T): T[] {
return [...arr].sort((a, b) =>
a[clave] < b[clave] ? -1 : a[clave] > b[clave] ? 1 : 0
);
}
const productos = [
{ id: 3, nombre: "Teclado", precio: 89 },
{ id: 1, nombre: "Mouse", precio: 29 },
{ id: 2, nombre: "Monitor", precio: 299 },
];
const porPrecio = ordenarPor(productos, "precio");
console.log(porPrecio.map(p => p.nombre)); // ["Mouse", "Teclado", "Monitor"]Monitor
299
["Mouse", "Teclado", "Monitor"]typeof en TypeScript — inferir tipo de un valor
En TypeScript, typeof en contexto de tipo (no de expresión) devuelve el tipo de una variable o valor. Fundamental para trabajar con objetos cuyo tipo no has declarado explícitamente.
// typeof como expresión (JavaScript runtime) → string
const x = typeof 42; // "number"
const y = typeof "hola"; // "string"
// typeof como tipo (TypeScript compile-time) → el tipo del valor
const configuracion = {
apiUrl: "https://api.ejemplo.com",
timeout: 5000,
reintentos: 3,
debug: false,
};
// Inferimos el tipo del objeto sin declararlo manualmente
type Configuracion = typeof configuracion;
// {
// apiUrl: string;
// timeout: number;
// reintentos: number;
// debug: boolean;
// }
function cargarConfig(override: Partial<typeof configuracion>): typeof configuracion {
return { ...configuracion, ...override };
}
const devConfig = cargarConfig({ debug: true, timeout: 10000 });
console.log(devConfig.apiUrl); // "https://api.ejemplo.com"
// typeof también funciona con funciones
function sumar(a: number, b: number): number {
return a + b;
}
type FnSumar = typeof sumar; // (a: number, b: number) => number
// Puedes crear una variable del mismo tipo que una función
let operacion: FnSumar = sumar;
operacion = (a, b) => a * b; // también válido — cumple la misma firmahttps://api.ejemplo.comkeyof typeof obj — union de claves en runtime
La combinación keyof typeof obj es el patrón más frecuente para extraer las claves de un objeto en runtime como un tipo union, sin declarar una interfaz separada.
const COLORES = {
rojo: "#EF4444",
verde: "#10B981",
azul: "#3B82F6",
morado: "#7C3AED",
amarillo: "#F59E0B",
};
// keyof typeof COLORES = "rojo" | "verde" | "azul" | "morado" | "amarillo"
type NombreColor = keyof typeof COLORES;
function obtenerHex(color: NombreColor): string {
return COLORES[color];
}
console.log(obtenerHex("morado")); // "#7C3AED"
// obtenerHex("naranja"); // ❌ Error en compilación
// Patrón común: validar claves en runtime con el tipo derivado del objeto
const ENDPOINTS = {
usuarios: "/api/usuarios",
productos: "/api/productos",
categorias: "/api/categorias",
auth: "/api/auth",
};
type Endpoint = keyof typeof ENDPOINTS;
async function fetchEndpoint(endpoint: Endpoint): Promise<Response> {
const url = ENDPOINTS[endpoint];
console.log(`GET ${url}`);
return fetch(url);
}
// Solo acepta claves válidas — autocompletado incluido
// fetchEndpoint("usuarios");
// fetchEndpoint("auth");
// fetchEndpoint("pedidos"); // ❌ Error
// Función de validación en runtime que sincroniza con el tipo
function esEndpointValido(valor: string): valor is Endpoint {
return valor in ENDPOINTS;
}#7C3AEDas const — literal types y objetos readonly
as const transforma un objeto para que TypeScript infiera tipos literales en lugar de tipos generales, y convierte todas las propiedades en readonly. El resultado es el tipo más preciso posible.
// Sin as const — TypeScript infiere tipos amplios
const coloresSin = {
primario: "#7C3AED", // string — demasiado amplio
secundario: "#A78BFA", // string
};
// Con as const — TypeScript infiere el valor literal exacto
const colores = {
primario: "#7C3AED", // "#7C3AED" — tipo literal
secundario: "#A78BFA", // "#A78BFA" — tipo literal
} as const;
// typeof colores = {
// readonly primario: "#7C3AED";
// readonly secundario: "#A78BFA";
// }
// Arrays también se benefician
const DIFICULTADES = ["easy", "medium", "hard", "master"] as const;
// readonly ["easy", "medium", "hard", "master"]
type Dificultad = (typeof DIFICULTADES)[number];
// "easy" | "medium" | "hard" | "master"
function esDificultad(val: string): val is Dificultad {
return (DIFICULTADES as readonly string[]).includes(val);
}
console.log(esDificultad("hard")); // true
console.log(esDificultad("ultra")); // false
// Tuplas — as const fija la longitud y tipos posicionales
const coordenadas = [40.416775, -3.703790] as const;
// readonly [40.416775, -3.703790]
type Latitud = (typeof coordenadas)[0]; // 40.416775
type Longitud = (typeof coordenadas)[1]; // -3.703790true
falseCombinando — as const + keyof typeof para enums type-safe
La combinación de as const con keyof typeof es el reemplazo moderno de los enums de TypeScript. Es más flexible, más debuggeable en runtime y evita el código generado por los enums.
// Patrón de "enum" con as const — PREFERIBLE a enum en TypeScript moderno
const Rol = {
ADMIN: "admin",
EDITOR: "editor",
LECTOR: "lector",
MODERADOR: "moderador",
} as const;
// El tipo de los valores
type ValorRol = (typeof Rol)[keyof typeof Rol];
// "admin" | "editor" | "lector" | "moderador"
// El tipo de las claves
type ClaveRol = keyof typeof Rol;
// "ADMIN" | "EDITOR" | "LECTOR" | "MODERADOR"
interface Usuario {
id: number;
nombre: string;
rol: ValorRol; // solo acepta los valores válidos
}
function puedeBorrar(usuario: Usuario): boolean {
return usuario.rol === Rol.ADMIN || usuario.rol === Rol.MODERADOR;
}
const admin: Usuario = { id: 1, nombre: "Ana", rol: Rol.ADMIN };
const lector: Usuario = { id: 2, nombre: "Luis", rol: Rol.LECTOR };
console.log(puedeBorrar(admin)); // true
console.log(puedeBorrar(lector)); // false
// Ventaja sobre enum: el valor en runtime es un string legible
console.log(Rol.ADMIN); // "admin" — no un número opaco como en un enum numérico
// Combinar con Record para una tabla de permisos
const PERMISOS: Record<ValorRol, string[]> = {
admin: ["leer", "escribir", "borrar", "admin"],
editor: ["leer", "escribir"],
lector: ["leer"],
moderador: ["leer", "escribir", "moderar"],
};
console.log(PERMISOS[Rol.EDITOR]); // ["leer", "escribir"]true
false
admin
["leer", "escribir"]| Criterio | enum de TypeScript | as const + keyof typeof |
|---|---|---|
| Valor en runtime | Número opaco (enum numérico) o string (string enum) | Siempre el valor declarado, legible |
| Tree-shaking | Genera IIFE en JS — difícil de eliminar | Objeto simple — tree-shakeable |
| Extensibilidad | No se puede extender en runtime | Se puede usar spread para extender |
| Type safety | Excelente | Excelente — equivalente |