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+
Interfejsy to kolejny temat, który będziemy
poruszać w tym kursie. W prostych słowach są one pewnego rodzaju kontraktem
opisującym to w jaki sposób ma wyglądać np.
typ, funkcja lub klasa. I to co jest istotne
to opisują one ich wygląd, ale nie mieszają się już do implementacji.
Najlepiej jednak będzie gdy pokażę Ci to na przykładzie.
Na ten moment wiemy już, że to jak wyglądają typy możemy opisywać poprzez
tzw. aliasy typów. No to skoro tak to na czym polega różnica?
A różnic jest tutaj przynajmniej kilka.
Pierwszą z nich jest fakt, że interfejsy tworzą nową nazwę typu.
Co to dokładnie oznacza zobaczmy
na przykładzie. Mamy tutaj typ Player, który wykorzystujemy w naszej funkcji.
Teraz analogicznie tworzę drugi typ Game,
który również wykorzystam w parametrze funkcji.
No i teraz po najechaniu na ten typ w tym miejscu,
otrzymuję informację o całej jego strukturze.
Natomiast w tym drugim przypadku otrzymuję skróconą informację o nazwie tego typu.
Także jest to pierwsza różnica pomiędzy interfejsami oraz typami.
Druga polega na tym, że w przypadku typów
mamy możliwość tworzenia właściwości, które mają dynamiczny klucz czyli np.
definiuję tutaj string literal type, które może przyjmować dwie wartości.
I teraz mogę wykorzystać ten typ korzystając z następującego zapisu.
Oznacza to dokładnie tyle, że typ Game
posiada dwie właściwości: finite i infinite.
W przypadku gdyby tych właściwości było
więcej tego typu zapis mógłby okazać się pomocny. W przypadku interfejsów
nie mamy takiej możliwości. Ale w zamian w przypadku interfejsów
możemy mówić o tak zwanym łączeniu interfejsów, czyli jeżeli tutaj zdefiniuję
jeden interfejs to definiując go ponownie poniżej otrzymam połączony interfejs.
Czyli jeżeli do tej funkcji przekażę teraz obiekt zawierający jedną właściwość,
od razu otrzymuję informację o tym, że muszę przekazać również drugą.
I teraz wszystko jest w porządku.
W przypadku gdybyśmy wykorzystali tutaj typy
taka konfiguracja nie byłaby możliwa.
Tymczasem kolejna przewaga interfejsów
polega na tym, że mogą po sobie dziedziczyć.
Zresztą odbywa się to na takiej samej zasadzie jak w przypadku klas.
Po prostu definiując jakiś interfejs możemy określić jaki interfejs ma rozszerzać.
Dzięki temu mamy naprawdę potężne
narzędzie do tego, aby określać nowe interfejsy.
Dodatkowo wewnątrz takiego interfejsu możemy określać właściwości opcjonalne,
czyli takie które mogą, ale nie muszą znajdować się w interfejsie.
Mało tego, gdyby zaistniała taka potrzeba,
możemy również rozszerzać wiele interfejsów jednocześnie.
Wystarczy, że ich nazwy podamy po przecinku.
W takiej sytuacji interfejs Superadmin
jest zobowiązany wypełnić kontrakty zarówno Admina, jak i Usera.
No i w ten oto sposób prezentują się
różnice pomiędzy interfejsami a aliasami typów.
Dodatkowo, jak widzisz, różnica pomiędzy interfejsami a klasami abstrakcyjnymi polega
na tym, że w klasach abstrakcyjnych możemy dodatkowo określić implementację
poszczególnych metod a w przypadku interfejsów, tak jak powiedziałem
na początku, możemy określać wyłącznie ich kształt.
A teraz, jeżeli już jesteśmy przy temacie
interfejsów, muszę powiedzieć Ci coś jeszcze na temat typów, co do tej pory
pomijałem. Mianowicie TypeScript do weryfikacji typów wykorzystuje
mechanizm, który nazywamy duck typing lub structural subtyping.
W prostych słowach oznacza to, że typy
weryfikowane są na podstawie tego jaki kształt posiada określona wartość.
Często do wyjaśnienia tego mechanizmu używa się pewnej analogii.
Zresztą to od niej pochodzi nazwa duck typing.
Czyli jeżeli mamy coś co kwacze jak
kaczka i chodzi jak kaczka to z dużą pewnością możemy stwierdzić, że faktycznie
jest to kaczka. Ale oczywiście kaczką być nie musi.
I wydaję mi się, że lepiej będzie jednak jak pokażę Ci to na przykładzie.
Mianowicie mam tutaj interfejs Readable,
który będzie określał dane, które da się przeczytać np.
książki. I zwróć uwagę, że jedyną wymaganą właściwością jest tutaj właściwość pages.
W momencie gdy zdefiniujemy drugi interfejs,
którym będzie już sama książka, będzie ona posiadać również dodatkowe właściwości np.
tytuł. No i teraz gdy utworzymy nowy obiekt na podstawie
tego interfejsu możemy wykorzystać go funkcji. Ale zwróć uwagę, że
ta funkcja tutaj oczekuje, że przekażemy do niej coś co da się przeczytać.
A jeżeli teraz pomimo tego przekażemy do
niej naszą książkę to nadal wszystko jest w porządku. I nawet sprawdzając to raz
jeszcze mamy tutaj typ Book a tutaj spodziewamy się typu Readable.
Pomimo tego dla TypeScriptu jest to jak najbardziej w porządku - w końcu książka
jest czymś co da się przeczytać, ale z pewnością też stuprocentowym typem Readable
jaki zdefiniowaliśmy tutaj nie jest. Bez wątpienia świadomość o tym w jaki sposób
weryfikowane są typy jest czymś o czym powinieneś wiedzieć.
W niektórych sytuacjach możesz wykorzystywać to na swoją korzyść
a w niektórych sytuacjach możesz być po prostu świadomy, że coś może pójść nie tak.
I na ten moment w kontekście interfejsów to wszystko.
Więc zapraszam Cię do kolejnej lekcji.
Duck typing · 5 min
// Duck typing // structural typing
interface Readable {
pages: number
}
interface Book {
pages: number
title: string
}
const book: Book = {
pages: 5,
title: "The one thing",
}
function read(something: Readable) {
return `Started reading ${something.pages} pages`
}
read(book) /*?*/Dziedziczenie interfejsów · 3 min
// Extends
interface Admin extends User {
is_admin: boolean
specialpowers?: boolean
}
// Extends multiple
interface Superadmin extends Admin, User {
godmode: boolean
}Merged interfaces · 2 min
// Merge
interface User {
name: string
}
interface User {
email: string
}
function info(user: User) {}
info({ name: "Adam", email: "adam@eduweb.pl" })Computed properties · 2 min
// Computed properties
type Types = "finite" | "infinite"
type Games = {
[type in Types]: string
}Type aliases vs Interface · 1 min
// Type aliases vs Interface
type Player = {
name: string
}
function show(player: Player) {}
interface Game {
name: string
}
function play(game: Game) {}