Generics in TypeScript

Generics in TypeScript

Dit artikel legt uit wat generics in TypeScript zijn.

YouTube Video

Generics in TypeScript

Generics in TypeScript is een functie waarmee je herbruikbare en type-veilige functies, klassen en interfaces kunt definiëren door typen te parametriseren. Door generics te gebruiken, kun je code schrijven die niet afhankelijk is van specifieke typen, zodat je dezelfde bewerkingen op verschillende typen kunt uitvoeren.

De basisprincipes van Generics

Generics werken als sjablonen die typen als argumenten accepteren, zodat functies en klassen met verschillende typen kunnen werken.

Generieke functies

Het volgende is een voorbeeld van een functie waarbij de argumenttypen worden gespecificeerd met behulp van generics.

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity<number>(42));       // 42
6console.log(identity<string>("Hello"));  // Hello
  • T is een generiek typeargument dat de typen van de argumenten en de retourneerwaarde van de functie vertegenwoordigt. Het daadwerkelijke type wordt bepaald op het moment dat de functie wordt aangeroepen.
  • Door expliciet <number> of <string> op te geven, specificeer je het type.

Generieke typen werken ook zonder expliciete specificatie, omdat TypeScript type-inferentie uitvoert.

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity(42));       // 42
6console.log(identity("Hello"));  // Hello
  • Zelfs zonder expliciet <number> of <string> te specificeren, vindt type-inferentie plaats. identity(42) wordt afgeleid als number, en identity("Hello") wordt afgeleid als string.

Beperkingen op Generics

Door beperkingen op generics te plaatsen, kun je ze beperken tot het accepteren van alleen specifieke typen.

1function loggingIdentity<T extends { length: number }>(arg: T): T {
2    console.log(arg.length);
3    return arg;
4}
5
6loggingIdentity("Hello");  // 5
7loggingIdentity([1, 2, 3]);  // 3
8
9// loggingIdentity(42);  // Error: number does not have a length property.
  • Het specificeren van T extends { length: number } geeft aan dat T een type met een length-eigenschap moet zijn. Daarom worden typen zonder een length-eigenschap niet geaccepteerd.

Combinatie met keyof

Door generics te combineren met keyof, kun je op een type-veilige manier de namen van eigenschappen verkrijgen.

1function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
2  return obj[key];
3}
4
5const person = { name: "Bob", age: 30 };
6const personName = getProperty(person, "name"); // string
7console.log(personName);
8
9// const error = getProperty(person, "unknown"); // Error
  • K wordt beperkt door keyof T, wat betekent dat alleen bestaande sleutels in T kunnen worden opgegeven. Het opgeven van een niet-bestaande sleutel zal resulteren in een compileerfout.

Generieke klassen

Klassen kunnen ook worden gedefinieerd met behulp van generics. Generieke klassen bieden flexibele typen voor eigenschappen en methoden.

 1class Box<T> {
 2    private _value: T;
 3
 4    constructor(value: T) {
 5        this._value = value;
 6    }
 7
 8    public getValue(): T {
 9        return this._value;
10    }
11
12    public setValue(value: T): void {
13        this._value = value;
14    }
15}
16
17const numberBox = new Box<number>(100);
18console.log(numberBox.getValue());  // 100
19
20const stringBox = new Box<string>("Hello");
21console.log(stringBox.getValue());  // Hello
  • Box<T> verklaart het type T dat binnen de klasse wordt gebruikt als een generiek type. Dit maakt het mogelijk om dezelfde klasse opnieuw te gebruiken voor verschillende typen.

Generieke interfaces

Generics kunnen ook worden gebruikt met interfaces.

 1interface Pair<T, U> {
 2    first: T;
 3    second: U;
 4}
 5
 6const numberStringPair: Pair<number, string> = { first: 1, second: "One" };
 7console.log(numberStringPair);  // { first: 1, second: 'One' }
 8
 9const booleanArrayPair: Pair<boolean, number[]> = { first: true, second: [1, 2, 3] };
10console.log(booleanArrayPair);  // { first: true, second: [ 1, 2, 3 ] }
  • Door twee generieke typen te specificeren met Pair<T, U>, kun je een object definiëren met een combinatie van verschillende typen.

Standaardtype Argumenten

Het is ook mogelijk om een standaardtype voor generieke type-argumenten op te geven.

1function createArray<T = string>(length: number, value: T): T[] {
2    return Array(length).fill(value);
3}
4
5console.log(createArray(3, "a"));   // ['a', 'a', 'a']
6console.log(createArray(3, 100));   // [100, 100, 100]
  • We stellen het standaardtype-argument in op string met <T = string>. Als er geen type expliciet wordt opgegeven, zal T van het type string zijn.

Generieke Type-Aliassen

Generieken kunnen ook worden gebruikt als type-aliasen (type).

 1type Result<T> = {
 2    success: boolean;
 3    data: T;
 4};
 5
 6const successResult: Result<number> = { success: true, data: 42 };
 7const errorResult: Result<string> = { success: false, data: "Error occurred" };
 8
 9console.log(successResult);  // { success: true, data: 42 }
10console.log(errorResult);    // { success: false, data: 'Error occurred' }
  • Result<T> vertegenwoordigt een resultaatobject dat gegevens van het type T bevat. Op deze manier kun je flexibele type-aliasen maken met behulp van generieken.

Meerdere Generieke Types

Door gebruik te maken van meerdere generieke types kun je nog veelzijdigere functies en klassen definiëren.

1function merge<T, U>(obj1: T, obj2: U): T & U {
2    return { ...obj1, ...obj2 };
3}
4
5const person = { name: "Alice" };
6const job = { title: "Engineer" };
7
8const merged = merge(person, job);
9console.log(merged);  // { name: 'Alice', title: 'Engineer' }
  • De functie merge neemt twee verschillende types T en U en combineert deze om een nieuw object te retourneren.

Samenvatting

  • Generieken maken herbruikbare en type-veilige code mogelijk door types als parameters te behandelen.
  • Door generieken te gebruiken in functies, klassen en interfaces, kun je flexibele logica schrijven die verschillende types verwerkt.
  • Door beperkingen aan type-argumenten toe te voegen of standaardtype argumenten in te stellen, kun je de reikwijdte van generieken beheersen.

Door gebruik te maken van generieken, kun je type-onafhankelijke, algemene code schrijven en optimaal profiteren van TypeScript's krachtige type-systeem.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video