Full Stack

Tipi avanzati in TypeScript

Autore

Manuel Ricci

Dopo aver esplorato i fondamenti con i tipi di base in TypeScript, è il momento di immergersi nelle acque più profonde e navigare attraverso i concetti più avanzati che questo linguaggio ha da offrire. Gli "Tipi Avanzati" di TypeScript non sono solo una continuazione naturale del viaggio iniziato con i tipi di base, ma rappresentano anche un salto qualitativo nella nostra capacità di esprimere con precisione e flessibilità le intenzioni del codice. Questo approfondimento ci porterà oltre le basi, esplorando Union Types, Intersection Types, Type Aliases, Literal Types e Nullable Types.

Mentre i tipi di base pongono le fondamenta, i tipi avanzati ci permettono di costruire strutture più complesse e dettagliate. Sono gli strumenti che rendono TypeScript estremamente potente in scenari di sviluppo complessi, consentendoci di definire con precisione la natura e il comportamento dei dati. Da una maggiore espressività nel definire i tipi a un controllo più raffinato su variabili e funzioni, i tipi avanzati aprono nuove possibilità per affrontare le sfide di programmazione in modo più efficace e sicuro.

Attraverso esempi pratici e analisi dettagliate, questo approfondimento servirà da ponte per passare dalla comprensione dei tipi di base a quella dei concetti più sofisticati in TypeScript. Preparati a esplorare la versatilità e la potenza che questi tipi avanzati possono portare al tuo arsenale di sviluppo.

Union Types

Gli Union Types in TypeScript permettono di definire una variabile che può essere uno tra diversi tipi. Questo è estremamente utile quando una variabile può essere di più di un tipo.

Definizione di Union Types:

1let mixedType: string | number;
2mixedType = "Hello"; // valido
3mixedType = 42;      // valido

Utilizzo in Funzioni:

1function combine(input1: string | number, input2: string | number) {
2    // ...
3}

Union Types in Condizioni:

1if (typeof mixedType === "string") {
2    console.log(mixedType.toUpperCase()); // Uso sicuro come stringa
3} else {
4    console.log(mixedType.toFixed(2));    // Uso sicuro come numero
5}

Intersection Types

Gli Intersection Types permettono di combinare più tipi in uno solo. Sono utili per unire set di proprietà di diversi tipi.

Definizione di Intersection Types:

1type Draggable = { drag: () => void };
2type Resizable = { resize: () => void };
3type UIWidget = Draggable & Resizable;

Utilizzo in Oggetti:

1let textBox: UIWidget = {
2    drag: () => { /* ... */ },
3    resize: () => { /* ... */ }
4};

Type Aliases

I Type Aliases permettono di dare un nome a un tipo, rendendo il codice più chiaro e facile da gestire.

Definizione di Type Alias:

1type Point = {
2    x: number;
3    y: number;
4};

Utilizzo in Funzioni e Variabili:

1function draw(coord: Point) {
2    // ...
3}
4let center: Point = { x: 0, y: 0 };

Literal Types

I Literal Types limitano i valori che un tipo può accettare a un insieme specifico di valori.

Definizione di Literal Types:

1type Direction = "up" | "down" | "left" | "right";

Utilizzo in Funzioni e Variabili:

1let move: Direction = "up";
2function setDirection(dir: Direction) {
3    // ...
4}

Nullable Types

I Nullable Types in TypeScript consentono di gestire null e undefined in modo più preciso, soprattutto quando si attiva strictNullChecks.

Uso con strictNullChecks:

1let str: string | null = null;
2str = "Hello"; // valido

Controllo e Gestione di null:

1function processString(s: string | null) {
2    if (s === null) {
3        // Gestione caso null
4    } else {
5        console.log(s.toUpperCase()); // Uso sicuro come stringa
6    }
7}

Type Assertions in TypeScript

Le Type Assertions in TypeScript sono un modo per comunicare al compilatore il tipo che ci si aspetta per una determinata espressione. Queste asserzioni non cambiano il tipo di runtime di una variabile; invece, permettono agli sviluppatori di utilizzare la loro conoscenza del codice per informare il compilatore sui tipi di scenari in cui il tipo non può essere automaticamente dedotto.

Le Type Assertions sono simili al casting di tipi in altri linguaggi di programmazione, ma differiscono perché non influenzano il codice a runtime. Sono utilizzate solo dal compilatore TypeScript per la verifica dei tipi.

Ci sono due modi per scrivere una Type Assertion in TypeScript:

  1. let myVariable = <Type>value;
  2. let myVariable = value as Type;

La scelta tra queste due sintassi dipende dalle preferenze personali e talvolta da considerazioni legate all'ambiente di sviluppo (ad esempio, JSX in React accetta solo la sintassi as).

Type Assertion con Variabili: Quando si conosce il tipo più specifico di una variabile.

1let someValue: any = "this is a string";
2let strLength: number = (someValue as string).length;

In questo esempio, si asserisce che someValue è una stringa, permettendo l'accesso sicuro alla proprietà .length.

Type Assertions in Accessi DOM: Comune in scenari come l'accesso agli elementi del DOM in un'applicazione web.

1let myCanvas = document.getElementById("mainCanvas") as HTMLCanvasElement;

Qui, l'asserzione indica che getElementById restituirà un HTMLCanvasElement, permettendo l'accesso alle proprietà e metodi specifici del canvas.

Asserzioni su Risposte di API: Utili quando si lavora con dati provenienti da API esterne o da fonti di dati dinamiche.

1interface User {
2    name: string;
3    age: number;
4}
5let userData = getUserData() as User;

Questo esempio presuppone che getUserData() restituisca un oggetto che si adatta all'interfaccia User.

Importanza e cautela

  • Le Type Assertions sono strumenti potenti, ma devono essere utilizzate con cautela. Un uso improprio può portare a errori difficili da individuare, perché si sta dicendo esplicitamente al compilatore di ignorare le sue normali regole di verifica dei tipi.
  • È importante utilizzarle solo quando si è certi del tipo di un oggetto, e non come soluzione per aggirare il sistema di tipizzazione di TypeScript.

Le Type Assertions forniscono un modo flessibile per lavorare con i tipi in TypeScript quando si conosce più informazioni sul tipo di una variabile rispetto a quanto il compilatore può dedurre. Tuttavia, è fondamentale utilizzarle in modo responsabile per mantenere l'integrità e la sicurezza del codice.

Type Guards in TypeScript

I Type Guards sono una funzione che TypeScript utilizza per determinare il tipo specifico di un'entità in un determinato blocco di codice. Questi sono particolarmente utili quando si lavora con tipi che non possono essere definiti in fase di compilazione e sono verificati solo durante l'esecuzione del codice.

Type Guard Personalizzato: Puoi creare una guardia di tipo personalizzata per verificare un tipo specifico.

1function isString(test: any): test is string {
2    return typeof test === "string";
3}
4
5let item: any = "test";
6if (isString(item)) {
7    console.log(item.length); // Nessun errore, item è garantito essere una stringa qui.
8}

Typeof Type Guard: TypeScript riconosce il guardia di tipo typeof per i tipi JavaScript di base.

1function padLeft(value: string, padding: string | number) {
2    if (typeof padding === "number") {
3        return Array(padding + 1).join(" ") + value;
4    }
5    if (typeof padding === "string") {
6        return padding + value;
7    }
8    throw new Error(`Expected string or number, got '${padding}'.`);
9}

Instanceof Type Guard: instanceof è un altro tipo di guardia che verifica se un oggetto appartiene a una particolare classe o costruttore.

1class Bird {
2    fly() {
3        console.log("Bird is flying");
4    }
5}
6
7class Fish {
8    swim() {
9        console.log("Fish is swimming");
10    }
11}
12
13function move(animal: Bird | Fish) {
14    if (animal instanceof Bird) {
15        animal.fly();
16    } else {
17        animal.swim();
18    }
19}

Generics in TypeScript

Le generics permettono di creare componenti che possono lavorare con diversi tipi, invece di un singolo tipo. Questo rende il codice più flessibile, mantenendo comunque la sicurezza del tipo.

Generics di Base: Le generics possono essere utilizzati per creare funzioni, interfacce e classi che lavorano su un tipo generico.

1function identity<T>(arg: T): T {
2    return arg;
3}
4
5let output = identity<string>("myString");  // output è di tipo 'string'

Generics con Array: I Generics sono spesso utilizzati per lavorare con array e collezioni, consentendo di mantenere il tipo degli elementi.

1function loggingIdentity<T>(arg: T[]): T[] {
2    console.log(arg.length);
3    return arg;
4}

Generics con Interfacce: Creare interfacce generiche consente di definire contratti generici per funzioni, classi e altre strutture.

1interface GenericIdentityFn<T> {
2    (arg: T): T;
3}
4
5function identity<T>(arg: T): T {
6    return arg;
7}
8
9let myIdentity: GenericIdentityFn<number> = identity;

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.