Tipi enumerativi in Typescript | Aulab

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":…

Lezione 17 / 30
Enza Neri
Immagine di copertina

Vuoi avviare una nuova carriera o fare un upgrade?

Trova il corso Digital & Tech più adatto a te nel nostro catalogo!

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.

Sei indeciso sul percorso? 💭

Parliamone! Scrivici su Whatsapp e risponderemo a tutte le tue domande per capire quale dei nostri corsi è il più adatto alle tue esigenze.

Oppure chiamaci al 800 128 626