
GUIDE PER ASPIRANTI PROGRAMMATORI
Tipi enumerativi in Typescript
A volte ci troviamo in presenza di tipi di dato il cui unico scopo è quello di discriminare tra un elenco finito di opzioni. Prendiamo ad esempio la seguente funzione, che presenta uno switch statement: function log(level: string, message: string): void { switch (level) { case "log": console.log(message); return; case "info": console.info(message); return; case "warning":…


Vuoi avviare una nuova carriera o fare un upgrade?
Trova il corso Digital & Tech più adatto a te nel nostro catalogo!
- Tipi primitivi in Typescript
- Tipi letterali in Typescript
- Null e undefined in Typescript
- Tipi enumerativi in Typescript
- Oggetti in Typescript
- Array e tuple in Typescript
- Funzioni in Typescript
- Guardie di tipo in Typescript
- Any vs unknown in Typescript
- Tipi algebrici in Typescript
- Parametri di tipo in Typescript
- Manipolazione di tipi in Typescript
- Tipi utility in Typescript
A volte ci troviamo in presenza di tipi di dato il cui unico scopo è quello di discriminare tra un elenco finito di opzioni.
Prendiamo ad esempio la seguente funzione, che presenta uno switch statement:
function log(level: string, message: string): void { switch (level) { case "log": console.log(message); return; case "info": console.info(message); return; case "warning": console.warn(message); return; case "error": console.error(message); return; } }
Lo scopo della funzione log è di unificare la chiamata ai diversi metodi di console, passando un parametro level il cui scopo è quello di discriminare quale livello di severità applicare. Il problema è che, definendo level: string, ammettiamo anche valori che non hanno senso rispetto ai casi definiti.
Un modo per evitare questo problema a runtime è aggiungere un caso default nel nostro blocco switch, e scatenare un’eccezione:
function log(level: string, message: string): void { switch (level) { case "log": console.log(message); return; case "info": console.info(message); return; case "warning": console.warn(message); return; case "error": console.error(message); return; default: throw new Error("Invalid log level value"); } }
Sicuramente considerare tutte le casistiche è un’ottima pratica, ma TypeScript può aiutarci con una dichiarazione di tipo molto speciale, introdotta dalla parola chiave enum:
enum LogLevel { Log, Info, Warning, Error } function log(level: LogLevel, message: string): void { switch (level) { case LogLevel.Log: console.log(message); return; case LogLevel.Info: console.info(message); return; case LogLevel.Warning: console.warn(message); return; case LogLevel.Error: console.error(message); return; } }
Quando dichiariamo un tipo enum, non facciamo altro che elencare i possibili valori che questo tipo può assumere, senza preoccuparci di individuare un valore corrispondente per le diverse opzioni. Proprio per questo motivo, la dichiarazione di tipi enum è così particolare: è uno dei rari casi in cui il compilatore scrive attivamente codice JavaScript al posto nostro, invece che limitarsi a verificare e rimuovere le dichiarazioni e le annotazioni di tipo.
Non è un caso che per l’esempio sia stato usato un costrutto switch: i tipi enum si prestano particolarmente per questo genere di casistiche, permettendo al type checker di limitare i valori alle sole opzioni definite dal tipo, e all’IDE di suggerirci l’autocompletamento di tutti i case.
Visto che i tipi enum comportano un’implementazione JavaScript, vediamo un attimo il codice generato dal compilatore per LogLevel:
var LogLevel; (function (LogLevel) { LogLevel[LogLevel["Log"] = 0] = "Log"; LogLevel[LogLevel["Info"] = 1] = "Info"; LogLevel[LogLevel["Warning"] = 2] = "Warning"; LogLevel[LogLevel["Error"] = 3] = "Error"; })(LogLevel || (LogLevel = {}));
Il risultato di questo stranissimo codice è, in effetti, molto più semplice di quello che sembra; questo è il valore di LogLevel dopo l’esecuzione di questa strana funzione:
{ '0': 'Log', '1': 'Info', '2': 'Warning', '3': 'Error', Log: 0, Info: 1, Warning: 2, Error: 3 }
In pratica, TypeScript ha creato un oggetto che mappa dei valori numerici ai nomi delle opzioni dell’enum, e poi mappa le opzioni dell’enum agli stessi valori numerici. Come mai questa strana simmetria?
Il punto è che, affinché JavaScript possa attribuire un significato alle opzioni enumerate, queste corrispondono in realtà a dei numeri che iniziano da 0, a salire. È anche possibile iniziare da numeri diversi da 0, oppure attribuire valori arbitrari ad ogni singola opzione:
enum OneToFour { One = 1, Two = 2, Three = 3 Four = 4 } enum PrimeNumbers { Two = 2, Three = 3, Five = 5 }
Grazie a questa implementazione così sofisticata, potremo accedere ai diversi valori dell’enum sia attraverso il loro indice numerico, sia attraverso il nome delle opzioni:
enum LogLevel { Log, Info, Warning, Error } const log = LogLevel["Log"]; console.log(log); // 0 const info = LogLevel[1]; console.log(info); // Info
TypeScript ci offre anche la possibilità di dare valori stringa alle opzioni dell’enum, il che semplifica il codice risultante:
enum LogLevel { Log = "Log", Info = "Info", Warning = "Warning", Error = "Error" } const log = LogLevel.Log; console.log(log); // Log
Questo codice risulta nel seguente JavaScript:
var LogLevel; (function (LogLevel) { LogLevel["Log"] = "Log"; LogLevel["Info"] = "Info"; LogLevel["Warning"] = "Warning"; LogLevel["Error"] = "Error"; })(LogLevel || (LogLevel = {})); var log = LogLevel.Log; console.log(log); // Log
In effetti, l’implementazione risulta più semplice perché, a questo punto, non è più necessario mappare gli indici numerici.
Gli enum sono molto pratici quando dobbiamo discriminare tra un numero finito di possibilità, e sono ampiamente utilizzati in linguaggi di programmazione tendenzialmente orientati agli oggetti come il linguaggio C# e il linguaggio Java. Ad ogni modo, poiché gli enum nascondono un’implementazione e, in fin dei conti, rappresentano dei normalissimi valori numerici o testuali dietro a un tipo, sono ritenuti abbastanza eccezionali in TypeScript e hanno un utilizzo circoscritto a determinate casistiche come quella mostrata nell’esempio o a stili di programmazione fortemente orientati agli oggetti.
Più avanti vedremo come combinare i tipi algebrici e quelli letterali per ottenere qualcosa di simile a dei tipi enum, ma senza la complessità introdotta dalla loro implementazione.
CONTENUTI GRATUITI IN EVIDENZA
Guide per aspiranti programmatori 👨🏻🚀
Vuoi muovere i primi passi nel Digital e Tech? Abbiamo preparato alcune guide per aiutarti a orientarti negli ambiti più richiesti oggi.