TypeScript Advanced Generics spiegato con esempi

I generici in TypeScript forniscono un modo per creare componenti di codice riutilizzabili e flessibili lavorando con una varietà di tipi di dati. I generici avanzati sviluppano ulteriormente questo concetto introducendo funzionalità aggiuntive come vincoli, valori predefiniti e tipi multipli, che consentono agli sviluppatori di scrivere codice più robusto e sicuro per i tipi. In questo articolo, verranno utilizzati esempi per esplorare questi concetti avanzati nei generici.

Vincoli generici

I vincoli limitano i tipi che un generico può accettare. Ciò assicura che il tipo passato a una funzione o classe generica soddisfi determinati criteri. Ad esempio, un vincolo può essere utilizzato per garantire che il tipo generico abbia una proprietà o un metodo specifici.

function getLength<T extends { length: number }>(arg: T): number {
    return arg.length;
}

const stringLength = getLength("TypeScript");
const arrayLength = getLength([1, 2, 3]);

In questo esempio, il vincolo <T extends { length: number }> garantisce che l'argomento passato a getLength abbia una proprietà length.

Generici multipli

TypeScript consente l'uso di più tipi generici nella stessa funzione, classe o interfaccia. Ciò è utile quando si lavora con coppie di valori o altre strutture dati che coinvolgono più tipi.

function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

const stringNumberPair = pair("TypeScript", 2024);

Questa funzione, pair, accetta due diversi tipi generici, T e U, e restituisce una tupla contenente entrambi i tipi.

Tipi generici predefiniti

I generici in TypeScript possono anche avere tipi predefiniti. Ciò è utile quando si desidera che un generico abbia un tipo di fallback se non viene fornito alcun tipo specifico.

function identity<T = string>(value: T): T {
    return value;
}

const defaultString = identity("Hello");  // T is string
const customNumber = identity<number>(100);  // T is number

In questo esempio, se non viene passato alcun tipo a identity, il valore predefinito è string.

Utilizzo di generici con interfacce

I generici possono essere usati con le interfacce per definire strutture complesse in cui i tipi non sono fissi. Ciò aggiunge flessibilità al modo in cui i dati vengono gestiti.

interface Container<T> {
    value: T;
}

const stringContainer: Container<string> = { value: "Hello" };
const numberContainer: Container<number> = { value: 42 };

L'interfaccia Container è progettata per contenere un valore di qualsiasi tipo, consentendo diversi tipi di contenitori con tipi specifici.

Classi generiche

Le classi in TypeScript possono anche essere generiche. Ciò è particolarmente utile quando si progettano classi che funzionano con vari tipi di dati, come classi di archiviazione dati o di raccolta.

class DataStore<T> {
    private data: T[] = [];

    add(item: T): void {
        this.data.push(item);
    }

    getAll(): T[] {
        return this.data;
    }
}

const stringStore = new DataStore<string>();
stringStore.add("Hello");
stringStore.add("TypeScript");

const numberStore = new DataStore<number>();
numberStore.add(42);

In questo esempio, la classe DataStore funziona con qualsiasi tipo di dati, fornendo un modo sicuro per archiviare e recuperare elementi.

Conclusione

I generici avanzati in TypeScript sono uno strumento potente per scrivere codice flessibile, riutilizzabile e sicuro per i tipi. Utilizzando vincoli, tipi multipli, valori predefiniti e generici in classi e interfacce, gli sviluppatori possono scrivere codice più complesso e robusto. La comprensione e l'utilizzo di questi concetti avanzati consente una maggiore flessibilità e garantisce la sicurezza dei tipi in tutte le applicazioni.