TypeScript: Differenze tra Tipi e Interfacce

L'articolo spiega le differenze tra tipi e interfacce in TypeScript. I tipi sono flessibili e ideali per rappresentare primitive, unioni e tipi di utilità, mentre le interfacce sono perfette per definire la struttura di oggetti e contratti, soprattutto con le classi. Scopri quando e come usarli per ottimizzare il tuo codice!

Ciao ragazzi! Oggi parliamo di un argomento che spesso crea confusione tra chi si avvicina a TypeScript: la differenza tra tipi e interfacce. Vediamo insieme come sfruttarli al meglio nei nostri progetti, con qualche esempio pratico.

Tipi in TypeScript

I tipi in TypeScript sono estremamente flessibili e permettono di rappresentare una vasta gamma di scenari. Vediamo insieme alcuni casi d'uso comuni:

  1. Primitive e Unions:

    I tipi possono rappresentare tipi primitivi come string, number, boolean, ecc. Ma non solo, possiamo anche creare tipi di unione per consentire a una variabile di avere più tipi. Comodo, no?

    type MyString = string;
    type MyNumber = number;
    type MyBoolean = boolean;
    
    type Status = 'success' | 'error';
    

    Qui sopra, Status può essere solo 'success' o 'error'. Tipo un semaforo, o va tutto bene o c'è un errore!

  2. Utility Types:

    TypeScript ci offre una serie di utility types predefiniti, come Partial, Readonly e Record, che sono davvero utili per manipolare e trasformare i tipi in modo elegante.

    type PartialPerson = Partial<Person>;
    type ReadonlyPerson = Readonly<Person>;
    type RecordOfNumbers = Record<string, number>;
    

    Vuoi rendere tutte le proprietà di un tipo opzionali o renderle solo in lettura? Questi utility types fanno proprio al caso tuo!

  3. Mapped Types:

    Con i mapped types possiamo trasformare le proprietà di un tipo esistente in maniera dinamica. Un esempio classico è rendere tutte le proprietà opzionali:

    type Optional<T> = { [K in keyof T]?: T[K] };
    

    È come dire a TypeScript: "Prendi tutte le chiavi di T e rendile opzionali". Fantastico, no?

Ok, prendiamoci un attimo per vedere un esempio pratico:

Immagina di avere un oggetto User in TypeScript con delle proprietà obbligatorie:

type User = {
  name: string;
  age: number;
};

Se provi a creare un oggetto di tipo User, devi specificare sia name che age. Ma se volessi rendere queste proprietà opzionali? Qui entra in gioco il tipo Optional.

type OptionalUser = Optional<User>;

Ecco cosa succede:

const user1: OptionalUser = {}; // Perfetto, entrambe le proprietà sono opzionali
const user2: OptionalUser = { name: 'Dario' }; // Ok, 'age' è opzionale
const user3: OptionalUser = { age: 32 }; // Ok, 'name' è opzionale
const user4: OptionalUser = { name: 'Dario', age: 32 }; // Va bene, entrambe le proprietà sono presenti

In sostanza, con Optional<T> tutte le proprietà diventano opzionali, rendendo i tuoi tipi più flessibili e adatti a diverse situazioni.

Interfacce in TypeScript

Passiamo ora alle interfacce. Sono lo strumento perfetto per definire contratti e forme di oggetti, soprattutto quando lavoriamo con le classi.

  1. Unione di Dichiarazioni:

    Una caratteristica fantastica delle interfacce è la possibilità di unire dichiarazioni. Puoi definire la stessa interfaccia più volte, e TypeScript le unirà automaticamente.

    interface Person {
      name: string;
    }
    
    interface Person {
      age: number;
    }
    
    // Ora Person ha sia 'name' che 'age'!
    
  2. Estendere Altre Interfacce:

    Le interfacce possono estendere altre interfacce. Questo promuove il riutilizzo del codice e rende il tuo codice più organizzato e comprensibile.

    interface Shape {
      color: string;
    }
    
    interface Circle extends Shape {
      radius: number;
    }
    

    Qui, Circle eredita color da Shape, aggiungendo la sua proprietà radius.

  3. Clausola Implements:

    Le interfacce sono perfette per definire contratti per le classi. Se una classe implementa un'interfaccia, deve aderire alla sua struttura, punto e basta!

    interface Logger {
      log(message: string): void;
    }
    
    class ConsoleLogger implements Logger {
      log(message: string) {
        console.log(message);
      }
    }
    

    In questo esempio, ConsoleLogger deve implementare il metodo log, perché lo richiede l'interfaccia Logger.

Somiglianze tra Tipi e Interfacce

  1. Tipizzazione Strutturale:

    Entrambi usano la tipizzazione strutturale. Significa che si basano sulla forma del tipo più che sulla sua dichiarazione esplicita. Basta che la struttura combaci, e TypeScript sarà felice!

  2. Tipi di Intersezione:

    Sia tipi che interfacce possono essere combinati con i tipi di intersezione (&). Un modo molto potente per creare nuovi tipi che uniscono le caratteristiche di più tipi o interfacce.

    type Employee = { id: number } & Person;
    

    In questo caso, Employee deve avere sia un id che tutte le proprietà di Person.

Conclusione

E quindi, cosa dovresti usare? Tipi o interfacce? Dipende! In generale, le interfacce sono preferibili per definire forme di oggetti e contratti (specialmente con le classi), mentre i tipi sono più flessibili e versatili, adatti per un'ampia gamma di scenari.

TypeScript è in costante evoluzione, quindi tieni d'occhio le ultime novità e sperimenta! L'importante è trovare ciò che funziona meglio per te e per il tuo progetto. Alla prossima, e buon coding! 🚀