od Podstaw
1 godz. 54 min · Typescript · Full-stack i Programowanie
Adam GospodarczykZacznijmy od tego, że JavaScript jest dynamicznie typowany. Oznacza to, że typy zmiennych mogą się zmieniać w czasie wykonywania programu, a my nie mamy nad nimi kontroli. A po co nam ta kontrola? Między innymi po to, aby przypadkowo nie dodać typu number do string lub przekazać do funkcji nieprawidłowe danych. Ogromną zaletą jest to, że o tego typu błędach dowiadujemy się już na etapie pisania kodu a nie dopiero w trakcie jego wykonywania... na produkcji.
Prawdopodobnie znasz mechanizm intellisense, czyli inteligentnego podpowiadania kodu przez edytor. Pisząc kod w TypeScript, tworzysz definicje typów dotyczących Twojej aplikacji. W ten sposób dostęp do definicji klas i właściwości obiektów masz właściwie na wyciągnięcie myszki. Wystarczy "hover" na funkcji aby uzyskać szczegółowe informacje, które pomogą Ci w pisaniu kodu. Oprócz tego otrzymasz również automatyczne podpowiedzi w trakcie dostępu do właściwości i metod. Nic tylko pisać kod!
Nie musisz przepisywać całego projektu. TypeScript stanowi nadzbiór JavaScriptu. Zatem cały Twój projekt jest z nim w 100% kompatybilny, a Ty możesz stopniowo dodawać definicje typów. Nawet nie będziesz wiedział kiedy typy pojawią się w całej Twojej aplikacji. To daje Ci również możliwość uczenia się TypeScriptu w trakcie jego wdrażania. W tym kursie opanujesz podstawy a później... sam zobaczysz!
Ten kurs został stworzony z myślą o programistach, którzy podstawy JavaScriptu mają już za sobą i chcą poznać zalety statycznego typowania oferowanego przez TypeScript. Na początku Kursu zaczynamy również od krótkiej powtórki najważniejszych elementów samego JS.
3.8+
Ostatnim krokiem przed rozpoczęciem nauki TypeScriptu będzie upewnienie się, że jesteśmy
na bieżąco ze zmianami w języku JavaScript, które określamy mianem ESNext.
Jedną z najbardziej zauważalnych zmian, które
pojawiły się w ciągu ostatnich lat jest sposób deklarowania zmiennych. Wcześniej w
tym celu wykorzystywaliśmy słowo kluczowe var.
W przypadku takiej zmiennej mogliśmy
zmieniać jej wartość oraz nawet ponownie deklarować.
I to akurat mogło być przyczyną wielu problemów.
Dodatkowo zmienne zadeklarowane słowem kluczowym var posiadają zakres funkcyjny.
Więc jeżeli wykorzystywaliśmy je w jakimś bloku np.
w bloku if lub pętli for to jeżeli wtedy
ich wartość ulegnie zmianie, to widzimy to również poza tym blokiem.
Aby uniknąć tego typu pułapek możemy teraz
zdeklarować zmienne, korzystając ze słowa kluczowego let. Tak zadeklarowanej zmiennej
nie możemy ponownie zadeklarować, ale możemy zmieniać jej wartość.
Dodatkowo posiada ona zakres blokowy, więc jeśli powtórzymy teraz nasz przykład to
pomimo tego, że jak widzisz zadeklarowaliśmy tutaj zmienną o tym samym
identyfikatorze, tylko z inną wartością, to poza tym blokiem wartość jej pozostaje
niezmieniona. I teraz poza słowem kluczowym let mamy do
dyspozycji jeszcze słowo kluczowe const. Zmienne zadeklarowane w ten sposób
zachowują się tak samo jak w przypadku słowa kluczowego let
z tą różnicą, że nie możemy zmieniać
wartości tych zmiennych. W momencie gdy spróbujemy to zrobić,
jak widzisz, otrzymujemy błąd informujący nas o tym, że nie możemy zmieniać wartości
tych zmiennych. W kontekście słów kluczowych let oraz const
istotny jest jeszcze hoisting, czyli mechanizm polegający na tym, że
deklarowana zmienna jest wynoszona na górę zakresu, do którego została przypisana.
Otóż w przypadku zmiennych zadeklarowanych słowem kluczowym var
mogliśmy odwołać się do zmiennej zanim ta została zadeklarowana.
Natomiast nie ma wątpliwości, że ten identyfikator tutaj już istnieje.
Taka sytuacja nie ma już miejsca w przypadku słów kluczowych let oraz const.
Tutaj jak widzisz otrzymujemy po prostu
reference error. W kontekście zmiennych należy zapamiętać
tylko tyle, że teraz korzystamy wyłącznie ze słów kluczowych let oraz const i zasada
jest taka, że domyślnie wykorzystujemy const.
Natomiast w sytuacji gdy wiemy, że wartość zmiennej powinna ulec zmianie, wtedy
dopiero wykorzystujemy let. Var natomiast obecnie praktycznie nie znajduje
żadnego zastosowania. Jeżeli chodzi o zmienne
warto jeszcze dodać tylko jeden mały fakt. Mianowicie zmienne zadeklarowane
słowem kluczowym const nie mogą ulegać zmianie.
Jednak jeżeli mówimy o właściwościach
obiektów lub elementach tablic ta zasada tutaj nie obowiązuje.
Wynika to z faktu, że zmienne zadeklarowane
w ten sposób nie mogą być ponownie zadeklarowane ani przypisane.
W tej sytuacji nie mówimy ani o jednym ani o drugim,
dlatego takie działanie jest możliwe.
Kolejnym stosunkowo nowym tematem są
template strings, czyli nowy format zapisu tekstu.
Wcześniej zmienne mogliśmy zapisywać korzystając z cudzysłowów lub apostrofów,
a teraz w przypadku template strings mówimy o backtickach.
Ich główną zaletą jest fakt, że możemy zapisywać zmienne bezpośrednio w stringu.
Dzięki temu łączenie danych jest zdecydowanie prostsze.
Drugą zaletą wykorzystania template string
jest możliwość zapisania tekstu w wielu liniach.
Jak widzisz nie ma z tym żadnego problemu.
Co ciekawe template string możemy wykorzystać jako
tzw. tag. Chodzi tutaj o to, że gdy zdefiniujemy sobie taką
funkcję, możemy podać jej nazwę a następnie template string.
Dzięki temu jako argumenty zostaną przekazane tutaj fragmenty tego tekstu
oraz poszczególne zmienne, które się w nim znajdują.
Taki zapis powinieneś kojarzyć chociażby ze styled components.
Kolejnym już tematem, który zdecydowanie kojarzy nam się z ESNext są funkcje
strzałkowe, czyli arrow function. Są to funkcje które wyglądają w ten sposób. Możemy je
wywoływać dokładnie tak, jak wywołujemy zwykłe funkcje.
Natomiast zapis bez wątpienia się tutaj różni. W tym przykładzie mamy tutaj
parametry oraz ciało funkcji a samą funkcję sygnalizujemy z pomocą fat arrow.
Tak zapisana funkcja strzałkowa może być odzwierciedlana normalnym zapisem w
następujący sposób. Efekt jaki tutaj uzyskaliśmy jest taki sam.
Jeżeli chodzi o funkcje strzałkowe warto jeszcze pamiętać o tym, że gdy przyjmują
one więcej niż jeden argument, musimy zapisać je w nawiasie.
Dodatkowo, jeżeli ciało takiej funkcji zapiszemy pomijając nawiasy klamrowe, cała
wartość, którą zapiszemy po strzałce, zostanie automatycznie zwrócona z tej funkcji.
Natomiast wtedy musimy pamiętać o słowie kluczowym return
do tego, aby zwrócić wartość.
I teraz do tego pierwszego zapisu istnieje jeden wyjątek.
Mianowicie chodzi o zwrócenie obiektu.
Tak zapisana funkcja nie jest poprawna tak
długo, dopóki nie zamkniemy tego obiektu w nawiasie.
Teraz wszystko jest ok. Poza różnicą samego
zapisu funkcja strzałkowa ma jeszcze jedną bardzo ważną cechę. Mianowicie jeżeli
zdefiniujemy sobie tutaj taki konstruktor, posiadający właściwość play, zwracający
właściwość name, to w momencie gdy na jego podstawie stworzymy nowy obiekt i wykonamy
tę metodę w taki sposób, to funkcja play zwróci nam
undefined.
Wynika to z faktu, że w tym kontekście this wskazuje na obiekt globalny.
Oczywiście w tej sytuacji nie jest to do końca to czego byśmy oczekiwali i aby
rozwiązać ten problem możemy po prostu zamienić tę funkcję na funkcję strzałkową.
I dopiero wtedy wynik będzie zgodny z oczekiwaniem.
Działa to dlatego, że w przypadku funkcji
strzałkowych this nie jest określane na podstawie kontekstu wykonania, tylko na
podstawie tego co otacza tę funkcję, a w tym przypadku jest to nasz obiekt.
Następnym tematem w kolejce są klasy, czyli
po prostu usprawnienie składni dla modelu dziedziczenia opartego o prototypy.
Klasy oczywiście mogą posiadać swoje właściwości i metody.
Dodatkowo możemy też rozszerzać klasy, korzystając z dziedziczenia.
Wtedy warto wiedzieć, że w sytuacji gdy chcemy wykonać konstruktor klasy, z której
dziedziczymy, musimy uruchomić metodę super. Więc w tej sytuacji, w momencie gdy
tworzę dodatkowe metody oraz instancje tej klasy, mam dostęp zarówno do właściwości
tej klasy, i w tym przypadku jest to właściwość name oraz do samego typu, czyli
mamy tutaj song, który pochodzi z tej klasy.
Nie ma najmniejszych wątpliwości, że zanim
rozpoczniesz naukę TypeScriptu zdecydowanie powinieneś wiedzieć co tutaj się dzieje.
Zanim przejdziemy dalej pamiętaj tylko o tym, że jeżeli teraz zrobimy tak
to klasa, którą tutaj mamy w przypadku
tak zwanego starego JavaScriptu, wyglądałaby w ten sposób.
Zatem jak widzisz mamy tutaj konstruktor oraz metody przypisane do prototypu.
Przechodząc już do kolejnego tematu możemy pomówić o operatorach rest oraz spread.
Mianowicie w przypadku funkcji zazwyczaj musimy wiedzieć dokładnie jakie parametry
ta funkcja posiada. Od jakiegoś czasu mamy jednak do dyspozycji parametr rest.
Dzięki niemu jesteśmy w stanie przekazać dowolną ilość argumentów do funkcji.
Jak widzisz po prostu te wszystkie
dodatkowe parametry zostały przekazane jako tablica.
Podobnie możemy korzystać z operatora spread.
Przykładowo jeżeli chcielibyśmy przekazać
elementy tej tablicy jako parametry funkcji to w momencie, gdy zrobimy tak,
nie do końca otrzymamy rezultat, o który nam chodzi.
Natomiast wystarczy zrobić tak, aby osiągnąć pożądany efekt.
Po prostu korzystając z takiego zapisu,
przekazaliśmy poszczególne elementy jako kolejne argumenty tej funkcji.
W tym wszystkim warto jeszcze wiedzieć, że taki operator możemy wykorzystać np.
do łączenia obiektów bądź też po prostu
przekazywania właściwości konkretnych obiektów.
No i podobnie możemy zastosować go w przypadku tablic.
Poza operatorami rest oraz spread możemy
korzystać również z destrukturyzacji. I pierwszy przykład destrukturyzacji może
wyglądać tak, że mamy tutaj elementy tablicy, które chciałbym zapisać w
zmiennych. Zamiast robić to ręcznie, odwołując się do poszczególnych elementów,
mogę wykorzystać destrukturyzację, aby
w jednej linii utworzyć cztery nowe zmienne. W ten sposób wartości zostały
przyporządkowane odpowiednio do tych identyfikatorów.
Podobnie sprawa wygląda z obiektami.
Możemy wyciągać poszczególne właściwości do zmiennych w taki sposób.
Dodatkowo to co tutaj zrobiłem to, jak widzisz po dwukropku, zapisałem tzw.
alias właściwości.
Dzięki temu wartość właściwości X zostanie zapisana w zmiennej horizontal.
No i oczywiście wszystko tutaj działa.
Taka destrukturyzacja obiektów bywa szczególnie
przydatna w momencie, gdy mamy do czynienia z parametrami funkcji.
Mianowicie wyobraź sobie, że mamy tutaj obiekt odpowiedzi, który zawiera zagnieżdżone dane.
Gdybyśmy napisali teraz funkcję obsługującą te dane, wewnątrz niej
musielibyśmy zadbać o odpowiedni dostęp do nich.
Ale korzystając tutaj z destrukturyzacji w ten
sposób, mam bezpośredni dostęp do właściwości value.
No i to wszystko sprawia, że destrukturyzacja jest niesamowicie przydatna.
Poza nią warto jeszcze wspomnieć o pętli
for of, bo z pewnością słyszałeś wcześniej o pętli for in, która iteruje po
elementach tablicy, dając nam dostęp do indeksu tych elementów.
W przypadku pętli for of jest całkiem
podobnie z tą różnicą, że mamy dostęp do wartości tych elementów.
W każdym razie for of to ciekawostka, która czasem może się przydać.
I tym sposobem przechodzimy do ostatniego tematu, czyli obietnic.
Obietnice stały się można powiedzieć
rewolucją w kontekście podejścia do kodu asynchronicznego ze względu na to, że
sprawdzają się w tym przypadku zdecydowanie lepiej niż callbacki.
Zobaczmy tutaj prosty przykład funkcji,
która zwraca obietnicę, wykonując proste sumowanie dwóch elementów.
Akcję tę zasymulujemy jako asynchroniczne, korzystając z funkcji setTimeout.
W przypadku obietnicy trzeba pamiętać o tym, że nie możemy ot tak odczytać jej
wartości. Próbując ją podejrzeć w taki sposób otrzymuje obiekt obietnicy.
Obietnica może mieć jeden z trzech stanów, czyli pending, fullfilled oraz rejected.
I teraz, aby odczytać jej wartość, musimy
posłużyć się metodą then. No i w tej sytuacji otrzymujemy stosowny komunikat.
Jednak gdyby z jakiegoś powodu obietnica
zakończyła się błędem, informację o tym błędzie moglibyśmy odczytać, korzystając z metody catch.
No i dokładnie tutaj mamy nasz wynik. Do obietnic jeszcze większą rewolucję
przyniosła składnia async await. W skrócie umożliwia ona pisanie kodu
asynchronicznego prawie tak samo wygodnie jak synchronicznego.
Aby skorzystać z tej składni musimy nasz kod
zamknąć w funkcji i poprzedzić ją słowem kluczowym async.
Oczywiście tylko w tej sytuacji jest to samowywołująca się funkcja,
natomiast tak być nie musi.
I teraz aby bezpośrednio odczytać wartość
tej obietnicy, poprzedzam ją słowem kluczowym await.
Dzięki temu już w kolejnej linii mogę odczytać jej wartość.
Tutaj oczywiście minusem jest fakt, że aby
wychwycić błąd z takiej obietnicy musielibyśmy skorzystać z bloku try catch.
No i tym sposobem kończymy powtórkę nowego JavaScriptu i przechodzimy już do TypeScriptu.
promise · 11 min
// Promise
const myPromise = function(a, b) {
return new Promise((res, rej) => {
const sum = a + b
setTimeout(res(sum), 500);
})
};
console.log(myPromise(1, 3));
myPromise(1, 2)
.then(result => `Promise finished with result: ${result}`) /*?*/
.catch(err => err); /*?*/
(async function() {
const result = await myPromise(3, 4);
console.log(result);
}());for of · 10 min
// for .. of
const items = ['a', 'b', 'c'];
for (let item in items) {
item;
}
for (let item of items) {
item;
}destructuring · 9 min
// Destructuring
const args = [1, 2, 3, 4];
const [a, b, c, d] = args;
console.log(a, b, c, d);
const coords = { x: 1, y: 10 };
const { x: horizontal, y } = coords;
horizontal; /*?*/
y; /*?*/
const response = { data: { value: 5 } };
function render({ data: { value } }) {
console.log(value);
}
render(response);rest i spread · 8 min
// Rest & spread
function manyParams(x, y, ...rest) {
console.log(x, y, rest);
}
// manyParams(1, 2, 3, 4, 5);
const args = [6, 7, 8, 9];
manyParams(args);
manyParams(...args);
const name = { name: 'eduweb' };
const website = { 'www': 'eduweb.pl' };
const profile = { ...name, ...website }; /*?*/
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5, 6]; /*?*/let i const · 2 min
// let & const
var foo = true;
foo = 10; /*?*/
var foo = 5; /*?*/
if (foo) {
var foo = false; /*?*/
}
foo; /*?*/
let bar = true;
if (bar) {
let bar = false; /*?*/
}
bar; /*?*/
const baz = true;
baz = false; /*?*/constructor function · 6 min
// Constructor function
function Song(name) {
this.name = name;
this.play = function() {
return this.name; /*?*/
}
}
const bestSongEver = new Song('Iron man');
setTimeout(bestSongEver.play);arrow functions · 5 min
// Arrow functions
const move = x => `moved to ${x}`;
move(5); /*?*/
const moveClassic = function(x) {
return 'moved to ' + x;
}
moveClassic(5); /*?*/
const multiParams = (x, y) => `moved to ${x} then y`;
const withBody = (x) => {
return `moved to ${x} then y`
};
const returnObj = (x) => ({
message: `moved to ${x} then y`
});template strings · 4 min
// Template strings
const user = 'eduweb';
const message = `Hello ${user}!`; /*?*/
const longMessage = `
First line...
...and a second one 🤯
`;
function tagged(literals, ...placeholders) {
console.log(literals, placeholders);
}
tagged`tag call is ${'OK'}. Nice, hm?`;hoisting · 3 min
// Hoisting
a; /*?*/
var a = 5;
// b; /*?*/
let b = 5;
const obj = {};
obj.id = 10;
obj.id; /*?*/klasy · 7 min
// Classes
class Media {
constructor(type) {
this.type = type;
}
}
class Song extends Media {
constructor(name) {
super('song');
this.name = name;
}
play() {
return `Playing ${this.type}: ${this.name}`;
}
}
(new Song('Iron man')).play(); /*?*/
var Song = (function () {
function Song(name) {
this.name = name;
}
Song.prototype.play = function () {
return "Playing " + this.name;
};
return Song;
}());