Generyki w TypeScript

Generyki w TypeScript

Ten artykuł wyjaśnia generyki w TypeScript.

YouTube Video

Generyki w TypeScript

Generyki w TypeScript to funkcja, która pozwala definiować wielokrotnego użytku oraz typowo-bezpieczne funkcje, klasy i interfejsy poprzez parametryzację typów. Używanie generyków pozwala pisać kod, który nie zależy od konkretnych typów, umożliwiając wykonywanie tych samych operacji na różnych typach.

Podstawy generyków

Generyki działają jak szablony, które akceptują typy jako argumenty, pozwalając funkcjom i klasom obsługiwać różne typy.

Funkcje generyczne

Poniżej znajduje się przykład funkcji, której typy argumentów zostały określone za pomocą generyków.

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity<number>(42));       // 42
6console.log(identity<string>("Hello"));  // Hello
  • T to argument typu generycznego, który reprezentuje typy argumentów funkcji oraz jej wartość zwracaną. Rzeczywisty typ jest określany w momencie wywołania funkcji.
  • Poprzez jawne określenie <number> lub <string> definiujesz typ.

Generyczne typy działają bez jawnego określenia, ponieważ TypeScript przeprowadza inferencję typów.

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity(42));       // 42
6console.log(identity("Hello"));  // Hello

Ograniczenia generyków

Poprzez nałożenie ograniczeń na generyki, możesz ograniczyć je do akceptowania tylko określonych typów.

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.
  • Określenie T extends { length: number } wskazuje, że T musi być typem posiadającym właściwość length. W związku z tym, typy bez właściwości length nie będą akceptowane.

Klasy generyczne

Klasy również mogą być definiowane przy użyciu generyków. Klasy generyczne oferują elastyczne typy dla właściwości i metod.

 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> deklaruje typ T używany w ramach klasy jako generyczny. Dzięki temu ta sama klasa może być wielokrotnie używana dla różnych typów.

Interfejsy generyczne

Generyki mogą być również używane z interfejsami.

 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 ] }
  • Określając dwa generyczne typy za pomocą Pair<T, U>, możesz zdefiniować obiekt zawierający kombinację różnych typów.

Domyślne argumenty typów

Możliwe jest również określenie domyślnego typu dla argumentów typów generycznych.

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]
  • Ustawiamy domyślny argument typu na string za pomocą <T = string>. Jeśli żaden typ nie zostanie jawnie określony, T będzie typu string.

Generyczne aliasy typów

Generyki mogą być również używane jako aliasy typów (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> reprezentuje obiekt wyniku zawierający dane typu T. W ten sposób możesz tworzyć elastyczne aliasy typów za pomocą generyków.

Wiele typów generycznych

Korzystając z wielu typów generycznych, możesz definiować jeszcze bardziej wszechstronne funkcje i klasy.

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' }
  • Funkcja merge przyjmuje dwa różne typy T i U, a następnie łączy je w nowy obiekt.

Podsumowanie

  • Generyki umożliwiają tworzenie wielokrotnego użytku i typ-bezpiecznego kodu, traktując typy jako parametry.
  • Korzystając z generyków w funkcjach, klasach i interfejsach, możesz tworzyć elastyczną logikę obsługującą różne typy.
  • Dodając ograniczenia do argumentów typów lub ustawiając domyślne argumenty typów, możesz kontrolować zakres generyków.

Za pomocą generyków możesz pisać niezależny od typu, uniwersalny kod, w pełni wykorzystując potężny system typów TypeScript.

Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.

YouTube Video