close
Cart icon
User menu icon
User icon
Skontaktuj się z nami:
+48 888-916-333
Lightbulb icon
Jak to działa?
FAQ icon
FAQ
Contact icon
Kontakt
Terms of service icon
Regulamin zakupów
Privacy policy icon
Polityka prywatności
Zdjęcie główne artykułu.

TypeScript - typy generyczne

Typy generyczne to ważna część języka TypeScript. Umożliwiają one tworzenie uniwersalnych komponentów (klas, funkcji, interfejsów). Ten mechanizm jednocześnie oferuje wszystkie korzyści płynące z kontroli typów. Dlatego warto się tym tematem poważnie zainteresować.

W tym artykule przedstawiamy ogólną ideą funkcji, klas oraz interfejsów generycznych. Mamy nadzieję, że udało się nam ten temat przedstawić w zrozumiały i czytelny sposób.

Funkcje generyczne

Załóżmy, że naszym zadaniem jest utworzenie całego zestawu funkcji. Dla każdego możliwego typu (string, number itp.) potrzebujemy funkcji, która zwraca wartość z wejścia. To bardzo pracochłonne zadanie, które skutkuje niepotrzebnym powielaniem kodu.

Musimy takich funkcji stworzyć naprawdę dużo. Poniżej pokazaliśmy tylko dwa wybrane przykłady:

					
function getValueAsnumber(arg: number) {
	return arg;
}

function getValueAsString(arg: string) {
	return arg;
}

//...
					
				

Zamiast tworzyć oddzielną funkcję dla każdego typu lepiej będzie użyć tzw. funkcji generycznej. Taka funkcja jest uniwersalna bo może przyjąć dane dowolnego typu - wystarczy tylko wskazać jaki to będzie typ.

					
function getValue<Type>(arg: Type): Type {
	return arg;
}
					
				

Powyższy fragment to właśnie funkcja generyczna. Element <Type> jest uniwersalny - funkcja może przyjąć dowolny typ danych. Co ciekawe, możesz te element nazwać dowolnie: Type, T itp. To już zależy od przyjętej konwencji nazewniczej.

Jak używamy takich funkcji? Popatrz:

					
let valueAsString = getValue<string>("Tom");
					
				

Wywołujemy funkcję i w miejscu <Type> podajemy <string>. Deklarujemy, że to wywołanie funkcji przyjmie string jako parametr. A teraz inne wywołanie tej funkcji:

					
let valueAsnumber = getValue <number> (1)
					
				

Tym razem deklarujemy, że to wywołanie funkcji przyjmie number. Czyli mamy uniwersalną funkcję, która może operować na dowolnym typie danych. Co ważne, wciąż mamy wszystkie benefity płynące z użycia typowania. Teoretycznie można by użyć prostej funkcji, która przyjmuje any (dowolny typ) . Ale robiąc coś takiego pozbywamy się na własne życzenie kontroli typów.

Klasy generyczne

Klasy generyczne działają na podobnej zasadzie. Możemy stworzyć uniwersalną klasę, która operuje na dowolnych typach. Oto przykładowy kod takiej klasy:

					
class Monitor<K, V> { 
	private key: K;
	private value: V;

	constructor(key: K, value: V) {
		this.key = key;
		this.value = value;
	}

	public display() {
		console.log("key: " + this.key + ", " +  "value: " + this.value);
	}
}

let hd = new Monitor<number, string>(720, "HD");
hd.display();
					
				

Zwróć uwagę na pola klasy: private key: K oraz private value: V. Nieprzypadkowo są nazwane w bardzo ogólny sposób i nie mają określonych typów. Tworzymy obiekt takiej generycznej klasy:

					
let hd = new Monitor<number, string>(720, "HD");
					
				

Za K podstawiamy liczbę 720 a za V podstawiamy string "HD". Bez problemu możesz podstawić inne wartości:

					
let small = new Monitor<string, string>("phone", "360");
					
				

Tutaj mamy obiekt tej samej klasy ale użyliśmy parametrów typu string: dla K oraz dla V.

Mamy więc uniwersalną klasę, która może operować na danych dowolnego typu.

Interfejsy generyczne

Zaliczyliśmy już funkcje oraz klasy generyczne. W TypeScript masz też do dyspozycji generyczne interfejsy. Oto prosty przykład:

					
interface Operation<A, B, C> {
	value: C;
	show(firstParam: A, secondParam: B): void;
}
					
				

Jak pewnie się domyślasz, w klasie implementującej ten interfejs, za A, B oraz C będziemy mogli podstawi konkretne typy danych. To jest tak ogólna idea typów generycznych. Spróbujmy więc stworzyć klasę na podstawie powyższego interfejsu.

					
interface Operation {
	value: C;
	show(firstParam: A, secondParam: B): void;
};
 
class OperationImpl implements Operation {
	public value : number = 7;
 
	public show(firstParam: string, secondParam: number): void {
		console.log("firstParam  = " + firstParam);
		console.log("secondParam = " + secondParam);
	}
}
					
				

Nasza klasa OperationImpl implementuje interfejs Operation. Zauważ, że w klasie wskazujemy konkretne typy danych: <string, number, number>. Oczywiście to nasze A, B oraz C z interfejsu generycznego. Sama idea jest więc dość prosta. Mamy uniwersalny interfejs, który może być zaimplementowany z różnymi typami danych.

Jak widzisz w typach generycznych nie ma nic szczególnie trudnego - przynajmniej jeśli chodzi o ogólny mechanizm działania.