Menu

Dicionário em TypeScript: index signatures, Record e Map

O TypeScript não tem uma classe de dicionário ou hashmap; você tipa uma busca por chave e valor com uma index signature, Record<K, V> ou Map<K, V>. Veja cada uma, como verificar se uma chave existe, adicionar, remover e iterar, e por que noUncheckedIndexedAccess importa para dicionários.

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

O TypeScript não tem uma classe separada de dicionário ou hashmap. Um dicionário é um objeto comum tipado com uma index signature, { [key: string]: number }, o mesmo tipo escrito como Record<string, number>, ou um Map<string, number>. Os três guardam valores por chave; eles diferem nos tipos de chave, na forma como chaves ausentes são tipadas e na serialização.

Para chaves string e dados no formato de JSON, um objeto com Record<string, T> é a escolha usual. Para chaves que não são strings, ou entradas adicionadas e removidas o tempo todo, use um Map.

Index signatures

Uma index signature, [key: KeyType]: ValueType, diz "qualquer chave deste tipo mapeia para um valor deste tipo". O nome da chave (key, name, userId) serve só como documentação. Os tipos de chave podem ser string, number, symbol, padrões de template literal ou unions desses.

Chaves numéricas são convertidas em strings pelo JavaScript, então um objeto { [id: number]: string } continua tendo chaves string em tempo de execução: Object.keys({ 1: "one" }) é [ '1' ]. A index signature numérica só restringe como você pode indexar no TypeScript.

Record<K, V>

Record<string, V> é uma forma curta de { [key: string]: V }. Com uma union de chaves literais no lugar de string, ele vira um dicionário fixo que precisa conter todas as chaves:

Deixar staging fora de urls é o erro de compilação TS2741 (Property 'staging' is missing...), o que faz de Record com chaves union uma tabela de consulta verificada. Partial torna cada valor number | undefined.

Map como hash map

Um Map aceita chaves de qualquer tipo, mantém a ordem de inserção, tem size e tipa chaves ausentes com honestidade: get retorna V | undefined.

Verificando se uma chave existe

Existem várias verificações, e elas não significam todas a mesma coisa:

VerificaçãoFunciona emCuidado com
Object.hasOwn(obj, key)objetosES2022; use Object.prototype.hasOwnProperty.call(obj, key) em targets mais antigos
key in objobjetosTambém é true para chaves herdadas como toString e constructor
obj[key] !== undefinedobjetosNão distingue uma chave ausente de uma guardada como undefined
if (obj[key])objetosTambém é false para os valores 0, "" e false
map.has(key)MapNão estreita um map.get(key) posterior
map.get(key) !== undefinedMapA mesma ressalva sobre undefined dos objetos

O problema das chaves herdadas é o motivo pelo qual chaves vindas do usuário, como "constructor" ou "__proto__", tornam objetos comuns arriscados como dicionários. Um Map não tem esse tipo de chave.

O problema do tipo de chave ausente

Em uma index signature ou em Record<string, T>, ler qualquer chave tem o tipo T, mesmo uma chave que não existe. O compilador deixa você chamar métodos em um valor que é undefined em tempo de execução:

A opção de compilador noUncheckedIndexedAccess resolve isso: com ela, colors["grass"] tem o tipo string | undefined e a chamada a toUpperCase é erro de compilação até você fazer a verificação. Ela não faz parte de strict, então precisa ser ativada separadamente no tsconfig.json; veja strict mode para as outras flags ao lado dela. Um Map não tem essa brecha, já que get sempre inclui undefined.

Adicionando, removendo e iterando

delete funciona em propriedades de index signature. Em uma propriedade nomeada obrigatória de um tipo de objeto, é o erro de compilação TS2790, The operand of a 'delete' operator must be optional.

Qual usar

NecessidadeUse
Chaves string, entrada ou saída em JSONRecord<string, T>
Um conjunto fixo e conhecido de chaves, todas obrigatóriasRecord<"a" | "b", T>
Propriedades nomeadas mais chaves extras arbitráriasum tipo de objeto com index signature
Chaves que são objetos, números que você quer manter como números, ou qualquer coisa que não seja stringMap<K, V>
Inserções e remoções frequentes, ou um tamanho que você lê com frequênciaMap<K, V>
Chaves que vêm dos usuáriosMap<K, V> (sem chaves herdadas)

Perguntas frequentes

Como criar um dicionário no TypeScript?

Tipe um objeto comum com uma index signature, const ages: { [name: string]: number } = {}, ou o equivalente Record<string, number>. Depois adicione entradas com ages["ada"] = 36. Para chaves que não são strings, ou para uma coleção com inserções e remoções frequentes, use new Map<string, number>().

O TypeScript tem HashMap?

Não com esse nome. O Map nativo do JavaScript é um hash map: Map<K, V> guarda pares chave e valor com busca rápida por chave, mantém a ordem de inserção e aceita chaves de qualquer tipo. Um objeto comum tipado como Record<string, V> é a outra escolha comum para chaves string.

Como verificar se uma chave existe em um dicionário no TypeScript?

Em um dicionário de objeto, use Object.hasOwn(dict, key) ou key in dict (que também enxerga propriedades herdadas como toString), ou leia o valor e compare com undefined. Em um Map, use map.has(key) ou verifique direto o resultado de map.get(key), já que has não estreita um get posterior.

Qual é a diferença entre index signature e Record?

{ [key: string]: T } e Record<string, T> descrevem o mesmo tipo. Record é mais curto e também aceita uma union de chaves específicas, Record<"a" | "b", T>, que exige todas as chaves. Uma index signature pode ser combinada com propriedades nomeadas no mesmo tipo de objeto, e a chave pode ter um nome que a documenta, como em { [userId: string]: User }.

Por que ler uma chave inexistente de um dicionário não dá erro?

Por padrão, dict[key] em uma index signature ou Record<string, T> tem o tipo T, mesmo que o valor seja undefined em tempo de execução para uma chave ausente. Ative noUncheckedIndexedAccess no tsconfig.json e o tipo passa a ser T | undefined, o que obriga uma verificação. strict não inclui essa opção.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR