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.