Menu

Enum in TypeScript: enum numerici, di stringhe e const

Un enum TypeScript è un insieme di costanti con nome, come enum Direction { Up, Down }. Scopri gli enum numerici e di stringhe, il JavaScript in cui viene compilato un enum, la mappatura inversa, come iterare su un enum, i const enum e quando una union di letterali stringa o un oggetto as const è la scelta migliore.

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

Un enum TypeScript è un insieme di costanti con nome. enum Direction { Up, Down, Left, Right } crea sia un tipo, Direction, sia un oggetto a runtime i cui membri raggiungi come Direction.Up. I membri sono numerati da 0 se non dai loro dei valori, e gli enum di stringhe danno a ogni membro una stringa leggibile.

Gli enum sono una delle poche funzionalità di TypeScript che non sono solo tipi: un enum diventa un vero oggetto JavaScript quando il codice viene compilato.

Enum numerici

Senza inizializzatori, i membri ricevono 0, 1, 2 e così via. Dai un numero al primo membro e gli altri proseguono da lì. Puoi anche impostare esplicitamente ogni valore, che è la scelta sicura quando i numeri vengono salvati in un database o inviati in rete.

Affidarsi alla numerazione automatica va bene per valori che non escono mai dal programma. Se l'ordine dei membri potrebbe cambiare e i numeri vengono salvati da qualche parte, inserire un membro in mezzo rinumera in silenzio tutto ciò che viene dopo.

In cosa viene compilato un enum

I tipi vengono cancellati, ma un enum no. Questo è il JavaScript che TypeScript emette per un enum numerico e uno di stringhe:

enum Direction { Up, Down, Left, Right }
enum Status { Active = "ACTIVE", Inactive = "INACTIVE" }
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
var Status;
(function (Status) {
    Status["Active"] = "ACTIVE";
    Status["Inactive"] = "INACTIVE";
})(Status || (Status = {}));

Direction["Up"] = 0 restituisce 0, quindi Direction[0] = "Up" viene impostato nella stessa istruzione. Un enum numerico mappa quindi in entrambe le direzioni: dal nome al numero e dal numero di nuovo al nome. È la mappatura inversa. Gli enum di stringhe mappano solo dai nomi ai valori.

L'oggetto Direction stampato ha otto chiavi: i quattro nomi e i quattro numeri. Conta non appena ci iteri sopra.

Enum di stringhe

Ogni membro di un enum di stringhe ha bisogno di un valore stringa esplicito. I valori compaiono così come sono in log, JSON e database, e questo rende gli enum di stringhe più facili da debuggare dei numeri.

Un enum di stringhe è nominale in un modo che sorprende molti: una stringa semplice non gli è assegnabile, anche quando il testo corrisponde al valore di un membro.

index.ts(7,5): error TS2820: Type '"ACTIVE"' is not assignable to type 'Status'. Did you mean 'Status.Inactive'?

(Il suggerimento nel messaggio è un'ipotesi del compilatore e qui è sbagliato; la soluzione è Status.Active.) Nell'altra direzione, un valore Status si può usare ovunque sia attesa una string. Quando i valori arrivano come stringhe, da JSON o da un form, convertili con un controllo come quello della sezione sul controllo dei valori più sotto.

Usare un enum come tipo

Il nome dell'enum è un tipo i cui valori sono i suoi membri. Combinato con switch, TypeScript verifica che ogni membro sia gestito quando la funzione deve restituire un valore:

Se viene aggiunto un nuovo membro a Shape senza un nuovo case, sides smette di compilare con TS2366, Function lacks ending return statement and return type does not include 'undefined'. La pagina su switch mostra il controllo di esaustività più rigoroso basato su never.

Le ultime righe mostrano una debolezza reale degli enum numerici. Un letterale numerico che non corrisponde a nessun membro, const level: Level = 99, è un errore di compilazione (TS2322), ma qualsiasi valore tipizzato number viene accettato, quindi 57 passa. Gli enum di stringhe non hanno questa falla.

Iterare su un enum

A runtime un enum è un oggetto, quindi Object.keys, Object.values e Object.entries funzionano. Per un enum di stringhe restituiscono esattamente i membri. Per un enum numerico restituiscono anche le voci della mappatura inversa, che vanno filtrate:

Per tipizzare una variabile come "uno dei nomi dei membri dell'enum", usa keyof typeof Direction, che è la union "Up" | "Down" | "Left" | "Right". Allora Direction[name] recupera il valore con piena sicurezza dei tipi.

Un enum di stringhe non ha mappatura inversa, quindi per ottenere il nome di un membro dal suo valore cerca tra le voci: Object.entries(Status).find(([, v]) => v === "ACTIVE")?.[0] è "Active", oppure undefined quando nessun membro ha quel valore.

Verificare se un valore è in un enum

I dati che arrivano da fuori dal programma sono una semplice string o un number. Una type guard li confronta con i valori dell'enum e li restringe al tipo dell'enum:

Evita raw as Status su input non affidabili: l'asserzione compila, ma a runtime non viene controllato nulla, quindi "DELETED" attraverserebbe il programma tipizzato come Status valido.

const enum

const enum chiede al compilatore di eliminare l'enum e scrivere il valore di ogni membro dove viene usato. A runtime non c'è alcun oggetto, quindi non si può iterare né usare la mappatura inversa.

const enum fa risparmiare qualche byte e una ricerca di proprietà, ma richiede che il compilatore veda la dichiarazione dell'enum quando compila ogni file che lo usa. Gli strumenti che traspilano un file alla volta, come Babel e swc, non vedono un const enum dichiarato in un altro file; il type stripping di Node rifiuta i const enum come ogni altro enum; e con isolatedModules o verbatimModuleSyntax TypeScript segnala l'errore TS2748 quando usi un const enum da un file di dichiarazione. La maggior parte del codice applicativo non ha bisogno dei const enum.

Enum vs tipo union vs oggetto as const

Ci sono tre modi comuni per definire un insieme fisso di valori:

enumUnion di letteraliOggetto as const
Esiste a runtimesì, un oggettonosì, un oggetto semplice
Iterare sui valoriObject.values (numerici: filtrare)no, non c'è nulla da iterareObject.values
Accetta un semplice "red"no (enum di stringhe)sìsì
Accesso per nome X.Redsìnosì
Mappatura inversasolo enum numericinono
Funziona con il type stripping di Nodenosìsì
Consentito da erasableSyntaxOnlynosìsì
Sintassi extra da imparareregole degli enum, const enumnessunail pattern con typeof

Molti team oggi partono da una union di letterali stringa, e passano all'oggetto as const quando servono i valori a runtime (per iterarli o costruire un menu a tendina). I motivi: le union sono tipi puri e spariscono dall'output; accettano le stringhe semplici che arrivano da JSON e API; e gli enum sono l'unico pezzo di TypeScript di tutti i giorni che non è "JavaScript più tipi cancellabili".

Quest'ultimo punto è diventato concreto. Node esegue direttamente i file .ts togliendo i tipi, e un enum non è qualcosa che possa togliere:

node status.ts
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode

Il flag di Node --experimental-transform-types fa funzionare gli enum, e l'opzione del compilatore erasableSyntaxOnly segnala ogni enum come errore TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., così un progetto può vietarli fin dall'inizio. Vedi eseguire TypeScript per come funziona il type stripping. Niente di tutto questo rende sbagliati gli enum: il codice compilato con tsc o con un bundler li esegue senza problemi, e un codebase che usa già gli enum guadagna poco a convertirli.

Domande frequenti

Che cos'è un enum in TypeScript?

Un insieme di costanti con nome che è sia un tipo sia un oggetto a runtime: enum Direction { Up, Down } ti permette di scrivere Direction.Up e di usare Direction come tipo di un parametro. A differenza della maggior parte delle funzionalità di TypeScript, un enum non viene cancellato: compila in un oggetto JavaScript che esiste a runtime.

Come si itera su un enum in TypeScript?

Per un enum di stringhe, Object.values(MyEnum) dà i valori e Object.keys(MyEnum) i nomi. Un enum numerico contiene anche le voci della mappatura inversa ("0": "Up"), quindi filtrale: Object.keys(Direction).filter((k) => isNaN(Number(k))) dà solo i nomi. Un const enum non si può iterare, perché non esiste a runtime.

Come si converte una stringa in un valore di enum in TypeScript?

Confronta la stringa con i valori dell'enum in una type guard: function isStatus(s: string): s is Status { return (Object.values(Status) as string[]).includes(s); }. Dopo il controllo, s ha tipo Status. Un semplice s as Status compila ma non controlla nulla a runtime.

Meglio un enum o un tipo union in TypeScript?

Molti team preferiscono una union di letterali stringa (type Status = "active" | "inactive"), o un oggetto as const quando servono anche i valori a runtime. Le union vengono cancellate del tutto, funzionano con il type stripping integrato di Node e con l'opzione erasableSyntaxOnly, e accettano stringhe semplici come "active". Anche gli enum vanno bene, soprattutto nei codebase che li usano già.

Che differenza c'è tra enum e const enum?

Un enum normale compila in un oggetto che puoi iterare e consultare a runtime. Un const enum viene rimosso durante la compilazione e ogni uso viene sostituito dal suo valore (Size.Large diventa 2), quindi non costa nulla a runtime ma non si può iterare, e gli strumenti che compilano un file alla volta lo limitano.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA