Menu

keyof em TypeScript: as chaves de um objeto como tipo

O keyof recebe um tipo de objeto e dá a union dos nomes das propriedades dele. Veja keyof com interfaces, keyof typeof para objetos simples, acesso tipado a propriedades com generics, index signatures (string | number) e por que Object.keys retorna string[].

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

O keyof recebe um tipo de objeto e produz a union dos nomes das propriedades dele. keyof User abaixo é o tipo "id" | "name" | "email", então uma variável desse tipo só pode guardar uma dessas três strings.

O keyof existe só no sistema de tipos. Ele é apagado quando o código compila, então nunca verifica nada em tempo de execução. O que ele dá é uma garantia em tempo de compilação de que uma chave nomeia uma propriedade real: renomeie email na interface e todo "email" tipado como keyof User vira um erro.

keyof typeof: as chaves de um objeto simples

O keyof funciona com tipos, não com valores. Para um objeto que você escreveu como valor, obtenha primeiro o tipo dele com typeof e depois pegue as chaves:

Leia de dentro para fora: typeof colors é { red: string; green: string; blue: string }, e o keyof disso é a union das chaves. Escrever keyof colors sem typeof falha com error TS2749: 'colors' refers to a value, but is being used as a type here. Did you mean 'typeof colors'?.

Esse é o jeito usual de derivar uma union a partir de uma tabela de consulta: adicione uma cor ao objeto e o tipo Color cresce junto. O typeof também tem um segundo significado, de tempo de execução; a página sobre typeof trata dos dois.

Acesso tipado a propriedades com generics

O uso mais comum do keyof é em uma restrição de generic. K extends keyof T diz "K é uma das chaves de T", e T[K] é o tipo da propriedade nessa chave:

O segundo type parameter importa. Com key: keyof T e sem K, o tipo de retorno é T[keyof T], a union de todos os tipos de propriedade (string | number | boolean aqui), então title.toUpperCase() falharia com error TS2339: Property 'toUpperCase' does not exist on type 'string | number | boolean'. Capturar a chave exata em K mantém o tipo exato da propriedade. T[K] é um indexed access type, descrito na página sobre indexed access types.

Indexando com uma string simples

Um erro muito comum aparece quando você indexa um objeto com uma string que veio de outro lugar:

O compilador mostra:

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'.

Qualquer string poderia chegar em read, inclusive "colour", e Settings não diz nada sobre essa chave. Duas soluções: aceitar só chaves reais, ou verificar uma string desconhecida antes de usá-la.

name is keyof Settings é um type predicate: quando a função retorna true, o TypeScript trata input como uma chave dentro do if. A página sobre type guards explica os predicates em detalhe.

keyof com index signatures: string | number

Para um tipo com index signature, o keyof retorna o tipo do índice, e uma index signature de string dá string | number, e não só string:

O JavaScript converte toda chave numérica de propriedade em string, então scores[42] e scores["42"] são a mesma propriedade. O TypeScript modela isso incluindo number. A saída também mostra uma regra do JavaScript: chaves que parecem inteiros são listadas primeiro, em ordem crescente, antes das outras chaves string. Quando uma função genérica precisa só das chaves string, escreva Extract<keyof T, string> ou string & keyof T; Exclude e Extract têm uma página própria.

Resultados do keyof em resumo

Tipokeyof dá
{ id: number; name: string }"id" | "name"
{ [key: string]: number }string | number
{ [index: number]: string }number
{}never
anystring | number | symbol
{ a: 1; b: 2 } | { a: 3; c: 4 }"a" (as chaves que todo membro tem)
{ a: 1 } & { c: 4 }"a" | "c" (as chaves de qualquer um)
string[]number mais o nome de cada método de array ("length", "push", ...)

As linhas de union e intersection parecem invertidas à primeira vista. Um valor de um union type pode ser qualquer um dos membros, então só as chaves presentes nos dois são seguras. Um valor de uma intersection tem tudo dos dois, então tem todas as chaves.

Por que Object.keys retorna string[]

Object.keys(obj) é tipado como string[], nunca (keyof T)[]. O motivo é a tipagem estrutural: um valor pode ter mais propriedades do que o tipo declarado.

p tem o tipo Point, mas tem uma chave z em tempo de execução. Se Object.keys prometesse ("x" | "y")[], código que dependesse disso estaria errado para p. O cast é seguro para objetos literais que você mesmo criou; para objetos que chegam de fora, mantenha as chaves como string e verifique-as.

keyof em mapped types

O keyof também é como você percorre as chaves de um tipo para montar um tipo novo. É assim que os utility types nativos Partial e Readonly são escritos:

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] visita cada chave, e T[K] lê o tipo da propriedade nessa chave. Mapped types têm uma página própria com modificadores e remapeamento de chaves.

Perguntas frequentes

O que o keyof faz no TypeScript?

keyof T produz uma union dos nomes das propriedades do tipo T. Para interface User { id: number; name: string }, keyof User é "id" | "name". Ele só funciona com tipos, em tempo de compilação, e não gera JavaScript.

O que é keyof typeof no TypeScript?

O keyof precisa de um tipo, mas um objeto simples como const colors = { red: "#f00" } é um valor. typeof colors transforma o valor no tipo dele, e keyof typeof colors então dá a union das chaves, aqui "red". Escrever keyof colors falha com o erro TS2749.

Por que o keyof retorna string | number?

Quando o tipo tem uma index signature de string, como { [key: string]: number }. O JavaScript converte chaves numéricas de propriedades em strings, então obj[42] também é um acesso válido, e o TypeScript inclui number no tipo das chaves. Use Extract<keyof T, string> ou string & keyof T se você quer só as chaves string.

Por que Object.keys retorna string[] em vez de (keyof T)[]?

Porque um objeto pode ter mais propriedades do que o tipo dele lista: um valor com um campo extra pode ser atribuído a um tipo mais estreito. Tipar o resultado como (keyof T)[] seria mentira para esses objetos. Se você sabe que o objeto tem exatamente as chaves declaradas, faça um cast: Object.keys(obj) as (keyof typeof obj)[].

Como corrigir "Element implicitly has an 'any' type because expression of type 'string' can't be used to index type"?

O índice é uma string simples, e o tipo do objeto não tem index signature de string. Tipe o parâmetro como keyof YourType em vez de string, ou verifique a string antes com um type guard que a estreite para keyof YourType, ou dê ao objeto uma index signature ou o tipo Record<string, V> se ele realmente aceita qualquer chave.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR