Menu

Record in TypeScript: Record<K, V> spiegato con esempi

Record<K, V> è il tipo oggetto le cui chiavi sono K e i cui valori sono tutti V. Impara Record con chiavi string e con chiavi in unione (tutte obbligatorie), Partial<Record>, come iterare su un Record con chiavi tipizzate, il problema della chiave mancante e quando usare una index signature o una Map.

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

Record<K, V> è un utility type integrato per un oggetto le cui chiavi hanno tipo K e i cui valori hanno tutti tipo V. Con chiavi string descrive un dizionario; con un'unione di chiavi letterali descrive un oggetto che deve avere esattamente quelle chiavi.

Record esiste solo nel sistema dei tipi. In fase di esecuzione entrambi gli oggetti sono normali oggetti JavaScript, quindi funzionano con oggetti letterali, spread, JSON.stringify e tutto ciò che accetta un oggetto.

Sintassi e definizione

Record<Keys, Value>

Keys deve essere qualcosa che può fare da chiave di un oggetto: string, number, symbol, un'unione di letterali stringa o numerici, o un template literal type. Value può essere qualsiasi tipo. L'intera definizione nella libreria standard di TypeScript è una riga, un mapped type:

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

keyof any è string | number | symbol, l'insieme di tutti i tipi di chiave possibili. [P in K]: T crea una proprietà di tipo T per ogni membro di K. Questo spiega entrambi i comportamenti descritti qui sotto: un K ampio come string produce una index signature (qualsiasi chiave), e un K unione produce una proprietà obbligatoria per ogni membro.

Chiavi in unione: ogni chiave è obbligatoria

Quando le chiavi sono un'unione di letterali, un Record deve elencarle tutte, e nessun'altra. Così il compilatore diventa una lista di controllo:

Aggiungi "cancelled" a Status ed entrambi gli oggetti smettono di compilare finché non dai al nuovo stato un'etichetta e un colore. Omettere una chiave, o aggiungerne una che non è nell'unione, è un errore di compilazione:

index.ts(4,7): error TS2741: Property 'error' is missing in type '{ idle: string; loading: string; success: string; }' but required in type 'Record<Status, string>'.
index.ts(11,54): error TS2353: Object literal may only specify known properties, and 'paused' does not exist in type 'Record<Status, string>'.

Lo stesso funziona con un enum di stringhe come tipo delle chiavi: Record<Color, string> richiede una voce per ogni membro dell'enum.

Record<string, T> e la chiave mancante

Con chiavi string, qualsiasi chiave è ammessa, e TypeScript tipizza ogni lettura come T, anche per una chiave che non c'è. In fase di esecuzione una chiave mancante dà undefined:

È il bug più comune con Record. Ci sono tre modi per gestirlo: controllare con in o Object.hasOwn prima di leggere, dichiarare il valore come V | undefined, oppure attivare l'opzione del compilatore noUncheckedIndexedAccess, che aggiunge | undefined a ogni lettura tramite index signature nel progetto. Un Record con chiavi in unione non ha questo problema, perché ogni chiave esiste di sicuro.

Partial<Record<K, V>>: solo alcune chiavi

Per usare un'unione di chiavi senza richiederle tutte, avvolgi il Record in Partial. Le letture restituiscono allora V | undefined, il che è onesto:

Una chiave scritta male come jp resta un errore, ed è questo il vantaggio rispetto a Record<string, string>.

Iterare su un Record

Object.keys, Object.values e Object.entries funzionano tutti. Il problema è il tipo delle chiavi: Object.keys restituisce string[] e Object.entries restituisce [string, V][], mai la tua unione di chiavi:

TypeScript tiene le chiavi come string di proposito: in fase di esecuzione un oggetto può avere più proprietà di quelle elencate dal suo tipo, quindi promettere Plan[] non sarebbe sicuro in generale. Per un oggetto che hai creato da un letterale, come seats, il cast è sicuro.

Costruire un Record dai dati

I Record sono il risultato abituale quando raggruppi o indicizzi un array. Parti da un oggetto vuoto con il tipo Record e riempilo:

Book["genre"] riutilizza l'unione dell'interfaccia come tipo delle chiavi, quindi aggiungere un genere a Book fa sì che byGenre richieda una nuova voce.

Record<string, unknown> e le interfacce

Record<string, unknown> è un tipo comune per "un oggetto qualsiasi con chiavi stringa". Accetta oggetti letterali e valori tipizzati con un alias type, ma un'interfaccia viene rifiutata:

index.ts(11,11): error TS2345: Argument of type 'User' is not assignable to parameter of type 'Record<string, unknown>'.
  Index signature for type 'string' is missing in type 'User'.

Le interfacce si possono estendere tramite la fusione delle dichiarazioni, quindi TypeScript non presume che siano compatibili con una index signature; gli alias di tipo non si possono riaprire, quindi un type User = { name: string } passerebbe. Le soluzioni abituali sono accettare object al suo posto (puoi comunque chiamare Object.keys su di esso), rendere la funzione generica (<T extends object>(obj: T)), oppure dichiarare User con type (la pagina interface vs type tratta le altre differenze).

Record vs index signature vs Map

Record<K, V>{ [key: string]: V }Map<K, V>
Esiste in esecuzioneno, un oggetto normaleno, un oggetto normalesì, una classe
Insieme fisso di chiavisì, con un K unionenono
Tipi di chiavestring, number, symbol, unioni di letterali, template patternstring, number, symbol, template patternqualsiasi, oggetti compresi
Tipo della lettura di una chiave mancanteV (con chiavi string)VV | undefined da get
Insieme a proprietà con nometramite intersezione &sì, nello stesso tipono
JSON e spreadsìsìno, prima va convertita
DimensioneObject.keys(r).lengthObject.keys(o).lengthm.size
Aggiunte e rimozioni frequentifunzionafunzionapensata per questo

Scegli Record con chiavi in unione ogni volta che l'insieme delle chiavi è noto: è l'unica opzione che controlla che ogni chiave sia presente. Per chiavi stringa aperte, Record<string, V> e una index signature sono intercambiabili, e molti codebase preferiscono Record per leggibilità. Ricorri a una Map quando le chiavi vengono aggiunte e rimosse in fase di esecuzione, quando le chiavi non sono stringhe, o quando ti servono la dimensione e l'ordine di inserimento senza lavoro in più.

Domande frequenti

Cos'è Record in TypeScript?

Record<K, V> è un utility type integrato per un oggetto le cui chiavi sono di tipo K e i cui valori sono tutti di tipo V. Record<string, number> è un oggetto con qualsiasi chiave string e valori number; Record<"en" | "de", string> è un oggetto con esattamente le chiavi en e de, entrambe string.

Che differenza c'è tra Record e Map in TypeScript?

Record è un tipo per un normale oggetto JavaScript, quindi funziona con oggetti letterali, JSON e spread, e sparisce in compilazione. Map è una classe che esiste in fase di esecuzione con get, set, has e size, mantiene l'ordine di inserimento di tutte le chiavi, accetta chiavi di qualsiasi tipo (anche oggetti), e get restituisce V | undefined. Usa un Record per dati fissi o con la forma di JSON e una Map per chiavi aggiunte e rimosse in esecuzione.

Che differenza c'è tra Record<string, T> e { [key: string]: T }?

Per i valori sono lo stesso tipo: Record<string, T> si espande in un tipo oggetto con una index signature di tipo string. Ci sono due piccole differenze: una index signature può avere un nome e stare accanto ad altre proprietà nello stesso tipo, e keyof Record<string, T> è string mentre keyof { [key: string]: T } è string | number.

Come itero su un Record in TypeScript?

Usa Object.entries(record) per le coppie chiave-valore, Object.keys per le chiavi e Object.values per i valori. Le chiavi arrivano come string, non come K, perché un oggetto può avere chiavi in più in fase di esecuzione. Quando il Record ha un'unione di chiavi note, fai un cast: (Object.keys(r) as Array<keyof typeof r>).

Come rendo obbligatorie solo alcune chiavi di un Record?

Con chiavi in unione, Record<K, V> richiede tutte le chiavi. Avvolgilo in Partial per renderle tutte opzionali: Partial<Record<Lang, string>>. Per un misto, usa un'intersezione: Record<"en", string> & Partial<Record<"de" | "fr", string>> richiede en e permette le altre.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA