keyof toma un tipo objeto y produce la unión de los nombres de sus propiedades. Abajo, keyof User es el tipo "id" | "name" | "email", así que una variable de ese tipo solo puede contener uno de esos tres strings.
keyof solo existe en el sistema de tipos. Se borra al compilar, así que nunca comprueba nada en tiempo de ejecución. Lo que te da es la garantía en compilación de que una clave nombra una propiedad real: si renombras email en la interfaz, cada "email" tipado como keyof User pasa a ser un error.
keyof typeof: las claves de un objeto normal
keyof trabaja con tipos, no con valores. Para un objeto que escribiste como valor, primero obtén su tipo con typeof y luego toma las claves:
Se lee de dentro hacia fuera: typeof colors es { red: string; green: string; blue: string }, y su keyof es la unión de claves. Escribir keyof colors sin typeof falla con error TS2749: 'colors' refers to a value, but is being used as a type here. Did you mean 'typeof colors'?.
Es la forma habitual de sacar una unión de una tabla de consulta: añades un color al objeto y el tipo Color crece con él. typeof tiene además un segundo significado en tiempo de ejecución; la página de typeof explica los dos.
Acceso tipado a propiedades con genéricos
El uso más común de keyof es como restricción genérica. K extends keyof T dice «K es una de las claves de T», y T[K] es el tipo de la propiedad en esa clave:
El segundo parámetro de tipo importa. Con key: keyof T y sin K, el tipo de retorno es T[keyof T], la unión de todos los tipos de las propiedades (aquí string | number | boolean), así que title.toUpperCase() fallaría con error TS2339: Property 'toUpperCase' does not exist on type 'string | number | boolean'. Capturar la clave exacta en K conserva el tipo exacto de la propiedad. T[K] es un indexed access type, que se explica en la página de indexed access types.
Indexar con un string cualquiera
Un error muy frecuente aparece cuando indexas un objeto con un string que viene de otra parte:
El compilador muestra:
index.ts(10,10): error TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Settings'.
No index signature with a parameter of type 'string' was found on type 'Settings'.
A read podría llegar cualquier string, incluido "colour", y Settings no dice nada de esa clave. Hay dos soluciones: aceptar solo claves reales, o comprobar un string desconocido antes de usarlo.
name is keyof Settings es un type predicate: cuando la función devuelve true, TypeScript trata input como una clave dentro del if. La página de type guards explica los predicados en detalle.
keyof con index signatures: string | number
Para un tipo con index signature, keyof devuelve el tipo del índice, y una index signature de tipo string da string | number, no solo string:
JavaScript convierte toda clave numérica en string, así que scores[42] y scores["42"] son la misma propiedad. TypeScript lo modela incluyendo number. La salida muestra también una regla de JavaScript: las claves que parecen enteros aparecen primero, en orden ascendente, antes que las demás claves string. Cuando una función genérica necesita solo claves string, escribe Extract<keyof T, string> o string & keyof T; Exclude y Extract tienen su propia página.
Resultados de keyof de un vistazo
| Tipo | keyof da |
|---|---|
{ id: number; name: string } | "id" | "name" |
{ [key: string]: number } | string | number |
{ [index: number]: string } | number |
{} | never |
any | string | number | symbol |
{ a: 1; b: 2 } | { a: 3; c: 4 } | "a" (las claves que tienen todos los miembros) |
{ a: 1 } & { c: 4 } | "a" | "c" (las claves de cualquiera de los dos) |
string[] | number más el nombre de cada método de array ("length", "push", ...) |
Las filas de la unión y la intersección parecen al revés al principio. Un valor de un tipo unión puede ser cualquiera de los miembros, así que solo son seguras las claves presentes en los dos. Un valor de una intersección tiene todo lo de ambos, así que tiene todas las claves.
Por qué Object.keys devuelve string[]
Object.keys(obj) está tipado como string[], nunca como (keyof T)[]. El motivo es el tipado estructural: un valor puede llevar más propiedades que las de su tipo declarado.
p está tipado como Point y, sin embargo, tiene una clave z en tiempo de ejecución. Si Object.keys prometiera ("x" | "y")[], el código que confiara en eso fallaría con p. El cast es seguro para los objetos literales que creaste tú; para los objetos que llegan de fuera, deja las claves como string y compruébalas.
keyof en mapped types
keyof es también la forma de recorrer las claves de un tipo para construir un tipo nuevo. Así están escritos los utility types integrados Partial y Readonly:
type MyPartial<T> = { [K in keyof T]?: T[K] };
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
interface User {
id: number;
name: string;
}
type UserDraft = MyPartial<User>; // { id?: number; name?: string }
[K in keyof T] recorre cada clave, y T[K] lee el tipo de la propiedad en esa clave. Los mapped types tienen su propia página, con modificadores y remapeo de claves.
Preguntas frecuentes
¿Qué hace keyof en TypeScript?
keyof T produce una unión con los nombres de las propiedades del tipo T. Para interface User { id: number; name: string }, keyof User es "id" | "name". Solo trabaja con tipos, en tiempo de compilación, y no genera JavaScript.
¿Qué es keyof typeof en TypeScript?
keyof necesita un tipo, pero un objeto normal como const colors = { red: "#f00" } es un valor. typeof colors convierte el valor en su tipo, y keyof typeof colors da la unión de sus claves, aquí "red". Escribir keyof colors falla con el error TS2749.
¿Por qué keyof devuelve string | number?
Cuando el tipo tiene una index signature de tipo string, como { [key: string]: number }. JavaScript convierte las claves numéricas en strings, así que obj[42] también es un acceso válido, y TypeScript incluye number en el tipo de las claves. Usa Extract<keyof T, string> o string & keyof T si solo quieres las claves string.
¿Por qué Object.keys devuelve string[] y no (keyof T)[]?
Porque un objeto puede tener más propiedades de las que indica su tipo: un valor con un campo de más se puede asignar a un tipo más estrecho. Tipar el resultado como (keyof T)[] sería mentir para esos objetos. Si sabes que el objeto tiene exactamente las claves declaradas, haz un cast: Object.keys(obj) as (keyof typeof obj)[].
¿Cómo arreglo "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type"?
El índice es un string cualquiera y el tipo del objeto no tiene index signature de tipo string. Tipa el parámetro como keyof YourType en lugar de string, o comprueba antes el string con un type guard que lo reduzca a keyof YourType, o dale al objeto una index signature o un tipo Record<string, V> si de verdad acepta cualquier clave.