TypeScript
13 - Génériques

Les Génériques

Problématique

Parfois nous souhaitons pouvoir traiter un type de données sans savoir à l'avance quel type de données il contiendra. C'est le cas notamment des paramètres de fonction.

Supposons que nous voulons une fonciton qui tri un tableau de nombres ou de chaînes de caractères.

Nous allons fonc faire :

function sortNumbers(array: number[]): number[] {
    return array.sort((a, b) => a - b);
}
 
function sortStrings(array: string[]): string[] {
    return array.sort();
}

Une solution serait de lister les types possibles.

function sort(array: number[] | string[]): number[] | string[] {
    if (typeof array[0] === "number") {
        return array.sort((a, b) => a - b);
    } else {
        return array.sort();
    }
}

Mais ce n'est pas très pratique. C'est peu lisible et difficilement maintenable. Notamment si nous voulons ajouter un autre type, les dates.

Les génériques à la rescousse

Les génériques permettent de créer des composants réutilisables qui peuvent fonctionner avec un grand nombre de types différents.

function sort<T>(array: T[]): T[] {
    return array.sort();
}

TypeScript comprend le type de array et l'applique à la fonction sort.

const numbers = [3.1, 1.2, 2.3];
const sortedNumbers = sort(Math.floor(numbers));  // [1, 2, 3]
 
const strings = ["c", "a", "b"];
const sortedStrings = sort(strings.toUpperCase);  // ["a", "b", "c"]
 
const dates = [new Date(), new Date(), new Date()];
const sortedDates = sort(dates);  // [date1, date2, date3]

La syntaxe est donc

function f<T>(paramètre: T): T {
    // Code
}

Vous pouvez mettre n'importe quel nom à la place de T.

function f<U>(paramètre: U): U {
    // Code
}

Etendre

Il est possible d'utiliser un générique, qui se base sur un type.

interface NamedEntity {
    name: string;
}
 
function extractNames<T extends NamedEntity>(items: T[]): string[] {
    return items.map(item => item.name);
}
 
// Utilisation avec un tableau d'objets qui respectent l'interface NamedEntity
const people = [
    { name: "Alice", age: 25 },
    { name: "Bob", age: 30 }
];
 
const names = extractNames(people);
console.log(names);  // Output: ["Alice", "Bob"]
 
const cities = [
    { "city": "Paris", population: 2200000 },
    { "city": "London", population: 8900000 }
];
 
const cityNames = extractNames(city); // Erreur de type car city ne contient pas de propriété name