Um indexed access type busca o tipo de uma propriedade em outro tipo, com a mesma sintaxe de colchetes que o JavaScript usa para acessar propriedades. Person["age"] é number, e Person["address"]["city"] é string.
A vantagem é ter uma única fonte da verdade. Age não é uma segunda cópia de number: se Person["age"] mudar para string, todo tipo escrito como Person["age"] muda junto. Indexed access types existem só em tempo de compilação; eles não geram JavaScript.
Chaves em union e T[keyof T]
O índice pode ser uma union de chaves. O resultado é a union dos tipos das propriedades:
Product[keyof Product] é o jeito, no nível dos tipos, de dizer "qualquer valor de propriedade de Product". Pedir uma chave que não existe é erro de compilação: Product["price"] dá error TS2339: Property 'price' does not exist on type 'Product'.
Propriedades opcionais incluem undefined
Com strictNullChecks (ligado com strict), o tipo de uma propriedade opcional inclui undefined, e o indexed access type dela também:
type Profile = { name: string; nickname?: string };
type Nick = Profile["nickname"]; // string | undefined
Para remover o undefined, envolva-o: NonNullable<Profile["nickname"]> é string.
Tipos de elemento de array com T[number]
Arrays são indexados por números, então indexar um tipo array com number dá o tipo do elemento. Isso também funciona em vários níveis:
Isso é útil quando um tipo vem de um lugar que você não controla, como um cliente de API gerado: ApiResponse["data"]["users"][number] dá nome ao tipo de usuário sem que ninguém o tenha exportado.
Tuplas: indexe pela posição
Um tipo de tupla pode ser indexado com uma posição específica, com number para a union de todos os tipos de elemento, e com "length" para o tamanho dela como literal type:
(typeof arr)[number]: uma union a partir de um array const
O uso mais comum no mundo real: manter uma lista de valores permitidos como array (para poder percorrê-la em tempo de execução) e derivar o union type dela (para que o compilador a verifique).
Duas partes fazem isso funcionar. O as const transforma o array em uma tupla readonly de literal types; sem ele, ROLES é string[] e (typeof ROLES)[number] é só string. Depois, o typeof transforma o valor em um tipo, e o [number] junta os elementos dele em uma union. Os parênteses são opcionais (typeof ROLES[number] significa o mesmo), mas deixam clara a ordem das operações.
Verificando uma string contra o array
Essa tupla readonly tem uma surpresa. O método includes dela só aceita o tipo do elemento, então você não pode passar uma string qualquer para ele:
index.ts(6,25): error TS2345: Argument of type 'string' is not assignable to parameter of type '"admin" | "editor" | "viewer"'.
Amplie o array para readonly string[] na verificação e transforme a função em um type guard, para que uma verificação bem-sucedida estreite o valor:
Esse padrão dá uma única lista para os dois trabalhos: a verificação em tempo de execução usa o array, e o tipo é derivado dele, então os dois não conseguem ficar dessincronizados.
Indexed access em generics: T[K]
Dentro de um generic, T[K] com K extends keyof T é o tipo de qualquer propriedade que quem chama escolheu:
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 28 },
];
const names = pluck(users, "name"); // string[]
const ages = pluck(users, "age"); // number[]
A restrição K extends keyof T é o que torna T[K] válido. Sem ela, o TypeScript não tem como saber que K é uma chave de T e mostra error TS2536: Type 'K' cannot be used to index type 'T'.
Sintaxe em resumo
| Escrito | Significa | Exemplo de resultado |
|---|---|---|
T["key"] | tipo de uma propriedade | Person["age"] é number |
T["a" | "b"] | union dos tipos dessas propriedades | string | number |
T[keyof T] | union de todos os tipos de propriedade | todo tipo de valor |
T["a"]["b"] | propriedade aninhada | Person["address"]["city"] |
Arr[number] | tipo do elemento do array | User[] dá User |
Tup[0] | elemento da tupla em uma posição | tipo do primeiro elemento |
Tup["length"] | tamanho da tupla como literal | 3 |
(typeof arr)[number] | union dos valores de um array const | "admin" | "editor" |
T[typeof key] | indexa com o tipo de uma constante | o mesmo que T["name"] |
O índice é sempre um tipo. Person[key], em que key é uma variável const, falha com dois erros: error TS2538: Type 'key' cannot be used as an index type. e error TS2749: 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?.
Perguntas frequentes
O que é um indexed access type no TypeScript?
Um tipo escrito T[K] que busca o tipo da propriedade K no tipo T. Com type Person = { name: string; age: number }, Person["age"] é number. Ele usa a mesma sintaxe de colchetes do acesso a propriedades no JavaScript, mas funciona com tipos, em tempo de compilação.
Como obter o tipo do elemento de um array no TypeScript?
Indexe o tipo do array com number: para type Users = User[], Users[number] é User. Para um valor array, combine com typeof: (typeof users)[number].
Como transformar um array de strings em um union type?
Declare o array com as const para que o TypeScript mantenha os valores literais, e depois indexe-o com number: const roles = ["admin", "editor"] as const; type Role = (typeof roles)[number]; dá "admin" | "editor". Sem as const, o tipo do elemento é só string.
Por que não posso usar uma variável como índice em um indexed access type?
O índice precisa ser um tipo. Person[key] com const key = "name" falha (erros TS2538 e TS2749) porque key é um valor. Escreva Person[typeof key], ou use o literal direto: Person["name"].
O que T[keyof T] significa?
Indexa T com a union de todas as chaves dele, então o resultado é a union de todos os tipos de propriedade. Para { a: string; b: number } é string | number. É o jeito, no nível dos tipos, de dizer "qualquer valor deste objeto".