Les génériques avancés de TypeScript expliqués avec des exemples

Les génériques dans TypeScript permettent de créer des composants de code réutilisables et flexibles en travaillant avec une variété de types de données. Les génériques avancés poussent ce concept plus loin en introduisant des fonctionnalités supplémentaires telles que des contraintes, des valeurs par défaut et des types multiples, qui permettent aux développeurs d'écrire du code plus robuste et plus sûr en termes de types. Dans cet article, des exemples seront utilisés pour explorer ces concepts avancés dans les génériques.

Contraintes génériques

Les contraintes limitent les types qu'un générique peut accepter. Cela garantit que le type transmis à une fonction ou une classe générique répond à certains critères. Par exemple, une contrainte peut être utilisée pour garantir que le type générique possède une propriété ou une méthode spécifique.

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

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

Dans cet exemple, la contrainte <T extends { length: number }> garantit que l'argument passé à getLength possède une propriété length.

Génériques multiples

TypeScript permet d'utiliser plusieurs types génériques dans la même fonction, classe ou interface. Cela est utile lorsque vous travaillez avec des paires de valeurs ou d'autres structures de données impliquant plusieurs types.

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

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

Cette fonction, pair, accepte deux types génériques différents, T et U, et renvoie un tuple contenant les deux types.

Types génériques par défaut

Les génériques dans TypeScript peuvent également avoir des types par défaut. Cela est utile lorsque vous souhaitez qu'un générique ait un type de secours si aucun type spécifique n'est fourni.

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

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

Dans cet exemple, si aucun type n'est passé à identity, la valeur par défaut est string.

Utilisation des génériques avec les interfaces

Les génériques peuvent être utilisés avec des interfaces pour définir des structures complexes où les types ne sont pas fixes. Cela ajoute de la flexibilité à la gestion des données.

interface Container<T> {
    value: T;
}

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

L'interface Container est conçue pour contenir une valeur de n'importe quel type, permettant différents types de conteneurs avec des types spécifiques.

Classes génériques

Les classes dans TypeScript peuvent également être génériques. Cela est particulièrement utile lors de la conception de classes qui fonctionnent avec différents types de données, telles que les classes de stockage de données ou de collection.

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);

Dans cet exemple, la classe DataStore fonctionne avec n'importe quel type de données, offrant un moyen sécurisé de stocker et de récupérer des éléments.

Conclusion

Les génériques avancés de TypeScript constituent un outil puissant pour écrire du code flexible, réutilisable et sécurisé. En utilisant des contraintes, des types multiples, des valeurs par défaut et des génériques dans les classes et les interfaces, les développeurs peuvent écrire du code plus complexe et plus robuste. La compréhension et l'utilisation de ces concepts avancés permettent une plus grande flexibilité et garantissent la sécurité des types dans toutes les applications.