Większość podstawowych cech TS’a mamy już za sobą. Nadszedł czas na te naprawdę ciekawe elementy języka. Dziś omówię typy generyczne. Znów, dla weteranów języków klasyczny (java, C#), zagadnienie będzie znajome. Natomiast dla ludzi, którzy przygodę z programowaniem zaczęli od JS’a, idea typów generycznych może wydawać się dziwna i niecodzienna.
Postaram się objaśnić wszystko w miarę klarownie, tak, żeby po lekturze tego posta, nikt nie miał wątpliwości co do tego czym są typy generyczne i jak ich używać.
To może od początku. Czym są typy generyczne (zwane również generykami, chociaż osobiście nie lubię tego tłumaczenia)? Tłumacząc najogólniej: to fragmenty kodu, które można wykorzystać wiele razy dla różnych rodzajów typów. Brzmi jak coś przydatnego, prawda?
Załóżmy, że mamy funkcję, która przyjmuje kilka argumentów. Z góry określamy typy argumentów jako liczby. Następnie piszemy drugą funkcję, która robi dokładnie to samo, ale dla argumentów typu string. Od razu widać, że coś jest nie tak. Najlepiej byłoby połączyć te dwie funkcje w jedną. Jeszcze gorzej gdy okazuje się, że funkcja ta mogłaby działać dla argumentów dowolnego typu i własnie tak chcielibyśmy aby działała.
Jednym sposobem aby to zrobić to ustawienie typów argumentów na any. Ale każdy powinien już wiedzieć, że to słabe rozwiązanie. W momencie gdy zaczynamy używać typu any, tracimy wszystko dobre co zyskujemy dzięki TSowi: wykrywanie błędów, podpowiadanie składni itp.
Tutaj do akcji wkraczają typy generyczne. Najlepiej przejdę od razu do przykładu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var logger = function<T>(input: T):T{ console.log(input) return input } logger(1); logger("string"); class Dog { constructor(public name:string){}; bark():void { console.log("Bark!"); }; }; var maja = new Dog("maja"); logger(maja); |
Najważniejsze w tym fragmencie są cztery pierwsze linijki, a właściwie to pierwsza z nich. Deklaracja prostej funkcji logger wykorzystuje typy generyczne. Funkcja przyjmuje jeden argument, który jest najpierw logowany do konsoli a następnie zwracany. Bardzo mocno naciągana funkcja, ale załóżmy, że do czegoś się przyda. Hipotetycznie, niezależnie od tego jakiego typu argument przekażemy, funkcja będzie działać i spełni swoją rolę.
Aby móc zaimplementować generyki do funkcji, potrzebuję jednej rzeczy. Jest to lista parametrów typowych (ang. type parameters). Przekazuję je wewnątrz nawiasów ostrych, które pojawiają się przed nawiasami okrągłymi przechowującymi zwykłe parametry. W moim przypadku przekazuję jeden parametr (i tak naprawdę rzadko zdarza się, żeby było potrzeba więcej) i nazywam go T . Mógłbym nazwać go jak chcę, ale wielka litera ‚t’ to nazwa utarta, coś jak i w pętli for.
Myślcie o parametrze typowym jako o zmiennej, która przechowywać będzie konkretny typ. To jak joker, którego mogę zagrać zamiast zwykłego typu. Wykorzystuję to wewnątrz listy zwykłych argumentów. Parametr input otrzymuje właśnie ten typ. Do tego funkcja logger, też zwraca dane typu T.
Teraz wystarczy wywołać zmienną logger i podać jej argument dowolnego typu. Mogą to być typy prymitywne zarówno jak i instancje klas. A najlepsze jest to, że TS cały czas rozumie z jakim typem ma do czynienia:
Nigdzie nie pojawia się any, wszystko pod kontrolą 🙂 . Dzięki typom generycznym, udało stworzyć się funkcję, która może przyjmować argumenty dowolnego typu i nie wprowadza to chaosu do aplikacji.
Jest jeszcze jedna notacja, którą możemy użyć aby wywołać funkcję generyczną. Tak naprawdę zachęcam do korzystania własnie z tego zapisu:
1 2 3 |
logger<number>(1); logger<string>("string"); logger<Dog>(maja); |
Przed listą argumentów wpisujemy w ostrych nawiasach to co chcemy przekazać do parametrów typowych. Przejrzystość i czytelność kodu, plus pięćset 🙂 . Jeśli do tej pory nie załapaliście na czym polegają generyki, mam nadzieję, że te trzy linijki kodu, trochę rozjaśniły.
Typy generyczne, mogą nie wydawać się aż tak przydatne, gdy myślimy o nich w kategoriach typów prymitywnych. Ale uwierzcie mi, posiadają one prawdziwą moc, która pokazuje się dopiero w momencie, kiedy zaczniemy używać ich wraz z typami klas.
O tym jak współgrają klasy z generykami opowiem jednak w kolejnym poście. Jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem :).