Menu

Map em TypeScript: Map<K, V>, get, set, has e iteração

Como usar Map em TypeScript: criar um Map<K, V> tipado, por que get retorna V | undefined, set, has e delete, iteração na ordem de inserção, chaves que são objetos, conversão de e para objetos e JSON, Map vs objeto vs Record e como tipar array.map().

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

Um Map em TypeScript é o Map nativo do JavaScript com chaves e valores tipados: Map<string, number> mapeia strings para números. Crie um com new Map<K, V>() e depois use set, get, has e delete. get retorna V | undefined, porque a chave pode não existir.

São os argumentos de tipo que tornam um Map útil no TypeScript: todo set é verificado e todo get retorna o tipo do valor. A última linha é um erro de compilação (TS2345); @ts-expect-error mantém o bloco rodando.

Esta página trata da coleção Map. Se você procurava array.map(), ele está na última seção.

Criando um Map

Os tipos vêm de argumentos de tipo, das entradas iniciais ou de uma anotação. As entradas iniciais são um array de tuplas [key, value] ou qualquer outra coisa que as gere.

Duas armadilhas de inferência:

  • new Map() sem argumentos de tipo e sem entradas é Map<any, any>. Nada do que você coloca ou tira é verificado. Sempre escreva new Map<K, V>().
  • Entradas com tipos de valor diferentes não inferem uma union. new Map([["a", 1], ["b", "x"]]) é o erro TS2769 (No overload matches this call). Escreva o tipo: new Map<string, number | string>([...]).

get retorna V | undefined

Um Map não pode garantir que uma chave existe, então get tem o tipo V | undefined. Com strict, você precisa lidar com o undefined antes de usar o valor como V:

index.ts(4,7): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
  Type 'undefined' is not assignable to type 'number'.

As correções, da mais comum para a menos comum:

O TypeScript não lembra que has retornou true quando você chama get depois. Verificar direto o resultado de get é mais curto e seguro. Uma non-null assertion, stock.get("apples")!, silencia o erro, mas não protege nada se a chave não existir.

Métodos do Map e seus tipos

MembroTipo em Map<K, V>Observações
new Map<K, V>(entries?)Map<K, V>entries: iterável de [K, V]
set(key, value)thisAdiciona ou substitui; pode ser encadeado
get(key)V | undefinedundefined quando a chave não existe
has(key)boolean
delete(key)booleantrue se algo foi removido
clear()voidRemove tudo
sizenumberUma propriedade, não um método
keys(), values()iteradores de K, VEspalhe em um array: [...map.keys()]
entries(), for...ofiterador de [K, V]Ordem de inserção
forEach((value, key) => ...)voidAtenção: o valor vem primeiro

Iterando um Map

Um Map é iterado na ordem de inserção. for...of sobre o map gera tuplas [key, value], com o tipo [K, V].

Definir uma chave que já existe atualiza o valor, mas mantém a posição original dela na ordem.

Chaves que são objetos e contagem

Qualquer valor pode ser chave, inclusive objetos e arrays. As chaves são comparadas como ===: dois objetos com o mesmo conteúdo são chaves diferentes. Um Map também é a forma padrão de contar ou agrupar itens.

Para usar o conteúdo de um objeto como chave, derive uma chave string ou numérica, como user.id ou `${x},${y}`.

Map vs objeto vs Record

Map<K, V>Objeto / Record<string, V>
Tipos de chavequalquer um, comparado como ===string (números viram strings), symbol
Tipo de uma chave ausenteget retorna V | undefinedobj[key] é V, a menos que noUncheckedIndexedAccess esteja ativada
Ordemordem de inserçãoquase sempre a de inserção, mas chaves que parecem inteiros vêm primeiro em ordem crescente
Tamanhomap.sizeObject.keys(obj).length
Inserções e remoções frequentesotimizado para issonão otimizado para isso
JSONnão diretamente (JSON.stringify(map) é "{}")direto
Sintaxe literal, desestruturaçãonãosim
Chaves herdadas por acidentenenhuma"toString" in {} é true

Regra prática: um Map para uma coleção cujas chaves são dados (ids de usuário, palavras, entradas de cache) e mudam em tempo de execução; um tipo de objeto ou Record para um conjunto fixo de chaves conhecidas e para tudo o que vai para JSON ou vem dele. A página de dicionário compara index signatures, Record e Map para buscas com chave string.

Convertendo Maps em objetos e JSON

As entradas de um Map não são propriedades, então JSON.stringify não as enxerga. Converta com Object.fromEntries e Object.entries:

JSON.parse retorna any, então o as Record<...> declara o que se espera que os dados sejam. Isso não é uma verificação em tempo de execução; valide JSON não confiável antes de confiar nesse tipo.

Tipando array.map()

Muitas buscas por "typescript map" se referem ao método de array, que transforma cada elemento e retorna um novo array. O tipo dele é inferido a partir do callback, então anotações raramente são necessárias:

Anotar o tipo de retorno do callback ((u): Option => ...) é a forma mais clara de declarar o tipo do resultado: uma propriedade ausente ou com nome errado no objeto retornado passa a ser erro de compilação no próprio callback.

Perguntas frequentes

Como criar um Map no TypeScript?

Passe os tipos da chave e do valor para o construtor: const ages = new Map<string, number>(). Com entradas iniciais, os tipos são inferidos: new Map([["ada", 36]]) é um Map<string, number>. Um new Map() sem tipos e sem entradas é Map<any, any>, o que desliga a verificação, então sempre informe os tipos.

Por que Map.get retorna undefined no TypeScript?

map.get(key) tem o tipo V | undefined porque a chave pode não existir. O TypeScript não liga um map.has(key) anterior a um get posterior, então mesmo depois do has você precisa tratar o undefined: guarde o resultado e verifique, ou use um valor padrão com ??.

Qual é a diferença entre Map e objeto no TypeScript?

Um Map aceita chaves de qualquer tipo (inclusive objetos), mantém a ordem de inserção, tem size e foi feito para inserções e remoções frequentes. Um objeto comum ou Record<string, V> só tem chaves string (e symbol), vira JSON diretamente e aceita sintaxe literal e desestruturação. Use Map para coleções dinâmicas com chave e objeto para formatos fixos e dados JSON.

Como converter um Map em objeto ou JSON no TypeScript?

Object.fromEntries(map) transforma um Map<string, V> em um objeto comum, que JSON.stringify consegue serializar. JSON.stringify(map) direto no Map dá "{}", porque as entradas de um Map não são propriedades. O caminho inverso é new Map(Object.entries(obj)).

Como tipar o callback de array.map no TypeScript?

Em geral não é preciso: items.map((item) => item.name) infere item a partir do array e o tipo do resultado a partir do que o callback retorna. Para forçar um tipo de resultado, passe-o como argumento de tipo, items.map<string>(...), ou anote o tipo de retorno do callback.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR