Menu

Dizionario in TypeScript: index signature, Record e Map

TypeScript non ha una classe dizionario o hashmap; tipizzi una tabella chiave-valore con una index signature, con Record<K, V> o con Map<K, V>. Scopri ciascuna, come verificare se una chiave esiste, aggiungere, eliminare e iterare, e perché noUncheckedIndexedAccess conta per i dizionari.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

TypeScript non ha una classe dizionario o hashmap separata. Un dizionario è un oggetto semplice tipizzato con una index signature, { [key: string]: number }, lo stesso tipo scritto Record<string, number>, oppure una Map<string, number>. Tutti e tre memorizzano valori per chiave; differiscono per i tipi di chiave, per come sono tipizzate le chiavi mancanti e per come vengono serializzati.

Per chiavi stringa e dati in forma JSON, un oggetto con Record<string, T> è la scelta abituale. Per chiavi non stringa, o per voci aggiunte e rimosse di continuo, usa una Map.

Index signature

Una index signature, [key: KeyType]: ValueType, dice "qualsiasi chiave di questo tipo corrisponde a un valore di questo tipo". Il nome della chiave (key, name, userId) è solo documentazione. I tipi di chiave possono essere string, number, symbol, pattern di template literal o union di questi.

Le chiavi numeriche vengono convertite in stringhe da JavaScript, quindi un oggetto { [id: number]: string } ha comunque chiavi stringa a runtime: Object.keys({ 1: "one" }) è [ '1' ]. La index signature numerica limita solo il modo in cui puoi indicizzarlo in TypeScript.

Record<K, V>

Record<string, V> è la forma breve di { [key: string]: V }. Con una union di chiavi letterali al posto di string, diventa un dizionario fisso che deve contenere ogni chiave:

Omettere staging da urls è l'errore di compilazione TS2741 (Property 'staging' is missing...), e questo rende Record con chiavi union una tabella di lookup controllata. Partial rende ogni valore number | undefined.

Map come hash map

Una Map accetta chiavi di qualsiasi tipo, mantiene l'ordine di inserimento, ha una size e tipizza onestamente le chiavi mancanti: get restituisce V | undefined.

Verificare se una chiave esiste

Esistono diversi controlli, e non significano tutti la stessa cosa:

ControlloFunziona suAttenzione a
Object.hasOwn(obj, key)oggettiES2022; usa Object.prototype.hasOwnProperty.call(obj, key) con target più vecchi
key in objoggettiVero anche per chiavi ereditate come toString e constructor
obj[key] !== undefinedoggettiNon distingue una chiave mancante da una memorizzata come undefined
if (obj[key])oggettiFalso anche per i valori 0, "" e false
map.has(key)MapNon restringe un map.get(key) successivo
map.get(key) !== undefinedMapStessa avvertenza su undefined degli oggetti

Il problema delle chiavi ereditate è il motivo per cui le chiavi fornite dagli utenti, come "constructor" o "__proto__", rendono rischiosi gli oggetti semplici come dizionari. Una Map non ha chiavi del genere.

Il problema del tipo delle chiavi mancanti

Su una index signature o su Record<string, T>, leggere qualsiasi chiave ha tipo T, anche una chiave che non esiste. Il compilatore ti lascia chiamare metodi su un valore che a runtime è undefined:

L'opzione del compilatore noUncheckedIndexedAccess risolve il problema: con essa, colors["grass"] ha tipo string | undefined e la chiamata a toUpperCase è un errore di compilazione finché non lo controlli. Non fa parte di strict, quindi va attivata separatamente in tsconfig.json; vedi strict mode per gli altri flag che le stanno accanto. Una Map non ha questa lacuna, perché get include sempre undefined.

Aggiungere, rimuovere e iterare

delete funziona sulle proprietà di una index signature. Su una proprietà nominata obbligatoria di un tipo oggetto è l'errore di compilazione TS2790, The operand of a 'delete' operator must be optional.

Quale usare

EsigenzaUsa
Chiavi stringa, JSON in ingresso o in uscitaRecord<string, T>
Un insieme fisso e noto di chiavi, tutte obbligatorieRecord<"a" | "b", T>
Proprietà nominate più chiavi extra arbitrarieun tipo oggetto con una index signature
Chiavi che sono oggetti, numeri da tenere come numeri o qualsiasi non stringaMap<K, V>
Aggiunte ed eliminazioni frequenti, o una dimensione che leggi spessoMap<K, V>
Chiavi che arrivano dagli utentiMap<K, V> (nessuna chiave ereditata)

Domande frequenti

Come si crea un dizionario in TypeScript?

Tipizza un oggetto semplice con una index signature, const ages: { [name: string]: number } = {}, o con l'equivalente Record<string, number>. Poi aggiungi le voci con ages["ada"] = 36. Per chiavi che non sono stringhe, o per una collezione con frequenti aggiunte ed eliminazioni, usa new Map<string, number>().

TypeScript ha una HashMap?

Non con quel nome. La Map integrata di JavaScript è una hash map: Map<K, V> memorizza coppie chiave-valore con ricerca veloce per chiave, mantiene l'ordine di inserimento e accetta qualsiasi tipo di chiave. Un oggetto semplice tipizzato come Record<string, V> è l'altra scelta comune per le chiavi stringa.

Come si verifica se una chiave esiste in un dizionario TypeScript?

Per un dizionario oggetto usa Object.hasOwn(dict, key) o key in dict (che vede anche le proprietà ereditate come toString), oppure leggi il valore e confrontalo con undefined. Per una Map, usa map.has(key), oppure controlla direttamente il risultato di map.get(key), perché has non restringe un get successivo.

Che differenza c'è tra una index signature e Record?

{ [key: string]: T } e Record<string, T> descrivono lo stesso tipo. Record è più corto e accetta anche una union di chiavi specifiche, Record<"a" | "b", T>, che richiede ogni chiave. Una index signature si può combinare con proprietà nominate in un unico tipo oggetto, e la sua chiave può avere un nome che la documenta, come in { [userId: string]: User }.

Perché leggere una chiave mancante di un dizionario non dà errore?

Di default, dict[key] su una index signature o su Record<string, T> ha tipo T, anche se a runtime il valore è undefined per una chiave mancante. Attiva noUncheckedIndexedAccess in tsconfig.json e il tipo diventa T | undefined, obbligandoti a un controllo. strict non include questa opzione.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA