Map no TypeScript: tipar, percorrer e salvar um Map | We.Inc
Tipe um Map<K, V> no TypeScript, percorra, converta para JSON e de volta, e decida entre Map, Record e um objeto simples. Teste um Map ao vivo no navegador.
No TypeScript, um Map é o Map comum do JavaScript com tipos para as chaves e os valores: new Map<string, number>(). As chaves podem ser qualquer valor, as entradas voltam na ordem em que foram adicionadas pela primeira vez, size as conta e get retorna V | undefined. JSON.stringify não serializa um Map, então converta antes.
Métodos do Map e o que o TypeScript retorna
Chamada
Faz
Tipo que você recebe (Map<string, number>)
map.set(k, v)
Adiciona ou atualiza uma entrada
Map<string, number> (encadeável)
map.get(k)
Lê um valor
number | undefined
map.has(k)
Verifica uma chave
map.delete(k)
Remove uma entrada
boolean (true se existia)
Conta as entradas
for (const [k, v] of map)
Percorre na ordem de inserção
k: string, v: number
O playground abaixo é um Map de verdade rodando no seu navegador. Adicione, atualize e apague entradas e observe três coisas: a ordem que as entradas mantêm, o que size informa e a diferença entre fazer stringify do próprio Map e de uma cópia convertida.
Digite uma chave e um número, depois clique em Definir para adicionar ou atualizar essa entrada.
Clique em Apagar para remover a chave da caixa de chave, ou em Limpar para esvaziar o Map.
Leia a lista de entradas, que segue a ordem de iteração do próprio Map.
Compare as duas linhas de JSON para ver por que um Map precisa ser convertido antes de você salvá-lo ou enviá-lo.
Passe os argumentos genéricos ao criar um Map vazio. new Map() sozinho vira Map<any, any>, e toda leitura posterior perde o tipo.
Verifique has() ou compare get() com undefined antes de usar um valor. Uma non-null assertion esconde o caso em que a chave realmente não existe.
Chaves que são objetos são comparadas por referência. Dois objetos com os mesmos campos são duas chaves diferentes, então use uma string de id quando quiser dizer o mesmo registro.
Para guardar um Map no localStorage, salve JSON.stringify([...map]) e reconstrua com new Map(JSON.parse(text)). O array de pares mantém chaves que não são string e a ordem.
Perguntas frequentes
Como declaro o tipo de um Map no TypeScript?
Passe os tipos da chave e do valor como argumentos genéricos: const prices = new Map<string, number>(). Para um campo ou parâmetro, escreva o tipo como Map<string, number>, ou ReadonlyMap<string, number> quando a função só deve ler.
Por que map.get() tem undefined no tipo de retorno?
Porque o compilador não tem como saber quais chaves estão no Map em um dado momento. get é tipado como V | undefined, então você estreita com um if, um fallback ?? ou um teste com has() antes de tratar o valor como V.
Por que JSON.stringify(map) retorna {}?
Um Map guarda as entradas em um slot interno, não como propriedades do objeto, então o JSON enxerga um objeto vazio. Converta antes: Object.fromEntries(map) para chaves string, ou [...map] para um array de pares que funciona com qualquer tipo de chave.
Devo usar um Map ou um Record?
Use um Record para um conjunto fixo de chaves nomeadas e para dados que vão e vêm em JSON. Use um Map quando as entradas entram e saem enquanto a aplicação roda, quando as chaves são números ou objetos, ou quando a ordem de inserção importa.
Atualizar uma chave a move para o final?
Não. Definir uma chave existente muda o valor, mas mantém a posição original. Para movê-la para o final, apague a chave e defina de novo.
Map, Record e um objeto simples
Um objeto simples herda de Object.prototype, então nomes como constructor já estão lá, e as chaves dele são sempre strings ou symbols. Um Map começa vazio, aceita qualquer valor como chave e informa a quantidade de entradas por meio de size.
Record é um tipo, não um contêiner. Ele descreve um objeto simples, então tudo o que foi dito acima sobre objetos vale para ele em tempo de execução, incluindo o suporte nativo a JSON que falta ao Map.
Como as chaves são comparadas
O Map compara chaves com a regra SameValueZero. O número 1 e a string "1" são chaves diferentes, NaN corresponde a NaN, e um objeto só corresponde a si mesmo. Um objeto simples transformaria 1 em "1" e juntaria os dois.
Isso faz do Map o cache natural para valores indexados por um elemento do DOM, uma função ou um objeto de requisição, casos em que um objeto precisaria de um id string inventado.
Tipando Maps em assinaturas de função
Aceite ReadonlyMap<K, V> em funções que só leem. Ele remove set, delete e clear do tipo, então a própria assinatura diz a quem chama que o Map não será alterado.
Quando você monta um Map a partir de um array de pares, o TypeScript infere os tipos pelos pares. Adicione um genérico explícito quando o array puder estar vazio ou quando os pares misturarem tipos que você quer manter separados.
Receitas de Map tipado
Conte palavras com um Map
O fallback ?? trata a primeira aparição de uma palavra, e é por isso que get retornar number | undefined ajuda em vez de atrapalhar.
Salve um Map como JSON e carregue de volta
Um array de pares mantém todas as entradas e a ordem delas. Object.fromEntries é a opção mais legível quando todas as chaves são strings.
Aceite um Map somente leitura
Dentro de total, chamar prices.set seria um erro de compilação, então quem chama pode passar o próprio Map sem fazer uma cópia defensiva.
Playground de Map ao vivo
Chave
Valor (número)
Definir
Apagar
Limpar
Redefinir
Entradas na ordem de iteração
O Map está vazio.
size: {n}
"{v}" adicionada.
"{v}" apagada.
"{v}" não está no Map, então delete retornou false.