fbpx
typescript kurs online

TypeScript – wprowadzenie

TypeScript jest to język programowania tworzony w modelu open-source będący semantycznie nadzbiorem JavaScriptu. Główny zespół odpowiadający za jego utrzymanie pracuje w Microsoft. Ten artykuł – 'TypeScript wprowadzenie’ przekaże Ci podstawową wiedze na temat tego języka

O co chodzi z semantycznym nadzbiorem? Składnia języka jest niemal identyczna z JavaScript. TypeScript dodaje trochę dodatkowych rzeczy jak na przykład statyczne typowanie czy interfejsy.

Można powiedzieć też, że każdy kod napisany w JavaScript możemy skopiować do pliku .ts (rozszerzenie dla plików TypeScript) i wszystko będzie działać poprawnie.

Nie jest to jednak do końca prawda, przynajmniej nie przy domyślnych ustawieniach kompilatora TypeScript. Jeśli zluzujemy nieco różne obostrzenia to dopiero wówczas KAŻDY kod JavaScript uruchomi się jako kod TypeScript.

TypeScript jest kompilowany do czystego JavaScriptu. Na przykład wspomniane typy, najważniejsza cecha TypeScript, nie występują w trakcie działania programu (runtime). Jeśli skompilujemy kod z pliku .ts do pliku .js i uruchomimy w przeglądarce to nie będzie miała ona pojęcia, jakie typy gdzie ustawiliśmy, bo one nie istnieją w ECMAScript.

Przykładowy kod

Prosta klasa TypeScript (nie przejmuj się jeśli nie rozumiesz do konca o co w niej chodzi) wygląda tak:

class Greeter {

   private name: string;

   constructor(name: string) {
       this.name = name;
   }

   public greet(): string {
       return `Hello ${this.name}!`;
   }

}


console.log(new Greeter("Pawel").greet());

i jest on kompilowany do poniższego kodu JavaScript

var Greeter = /** @class */ (function () {
   function Greeter(name) {
       this.name = name;
   }
   Greeter.prototype.greet = function () {
       return "Hello " + this.name + "!";
   };
   return Greeter;
}());
console.log(new Greeter("Pawel").greet());

Microsoft udostępnia kompilator online, który pozwala przetestować małe kawałki kodu TypeScript i sprawdzić na jako kod są one kompilowane. Zabawka ta znajduję się tutaj https://www.typescriptlang.org/play/

Dlaczego TypeScript?

Skoro więc kompiluje się on do JavaScript to, co to TypeScript ma takiego, że warto w nim pisać zamiast od razu tworzyć pliki .js i dlaczego korzystają z niego tacy giganci jak Google, Netflix, LinkedIn, Facebook i wiele, wiele innych firm?

Podpowiedź jest już w nazwie – Types – czyli wspomniane już typy. Koniec z zastanawianiem się, czy dana funkcja w JavaScript oczekuję 24, czy „24”. Czy może autor obsłużył oba przypadki? W TS mówimy wprost, że dana funkcja przyjmuje parametr typu tekstowego albo liczbowego. Oczywiście podstawowe typy tego typu to tylko początek. Możemy tworzyć własne. Koniec z komentowaniem funkcji w JavaScripcie „proszę, proszę przesyłaj do tej funkcji tylko liczby”.

Już ta jedna cecha w gigantyczny sposób ułatwia utrzymanie kod i pracę w dużych, rozbitych na wiele podprojektów, aplikacjach oraz ich rozwój.

Oczywiście typu to nie wszystko. TypeScript dostarcza też interfejsy, generyki, modyfikatory dostępu, abstrakcje… cały świat programowania obiektowego. Dzięki temu programiści z języków takich jak Java czy C# bardzo szybko odnajdą się w świecie TS i może on stanowić dla nich swoistą bramę do świata front-endu, tak jak było to w moim przypadku.

Dzięki temu popularność TypeScript rośnie nieprzerwanie od roku 2015, gdy na przykład Angular 2+, z którym to TypeScript był początkowo głównie kojarzony, zaczyna odnotowywać spadki. Coraz częściej TypeScripta zaczyna się używać z React czy NodeJS.

TypeScript wprowadzenie – Części składowe

TypeScript jako taki składa się z trzech częsci:

  • języka programowania jako takiego
  • serwer języka  – program, który karmi danymi Twoje IDE (bądź edytor kodu). Dodaje autouzupełnianie, sprawdza typy w locie, dodaje różnego rodzaju podpowiedzi do kodu, które potem IDE wyświetla. Tak więc jeśli w Visual Studio Code (bezdyskusyjnie najlepszy edytor kodu spośród tych darmowych) najedziesz na taki kawałek kodu TypeScript:
    let data = [ 1, 4, 5, 3];
    to pojawi się tooltip let data : number[], który podpowiada nam, że kompilator języka wykrył, że typ zmiennej data to tablica liczb.
  • kompilator – program, który karmi się plikami .ts, sprawdza typy i składnie. Zamienia to wszystko na (mniej lub bardziej) klasyczny JavaScript. Jest używany przez serwer języka bądź wołamy go sami w czasie budowania naszej aplikacji.

Wady

Po pierwsze TypeScript to tylko narzędzie, które to trzeba wiedzieć kiedy jest przydatne, a kiedy nie. Tworząc proste projekty może się okazać, że TypeScript będzie tylko nadmiarową warstwą do opanowania.

Kolejną sprawą jest fakt, że użycie TypeScript’u doda kolejny zestaw narzędzi do opanowania w już pokaźnym zestawie z jakiego trzeba korzystać tworząc aplikacje webowe oraz kolejny klocek do wpięcia w nasz proces budowy aplikacji, na przykład dodane kroku kompilacji TS do Webpacka. Co prawda obecnie jest to już całkiem wygodnie obsłużone, co nie zmienia jednak faktu, że to dodatkowa rzecz do nauki.

Dodatkowo dochodzi cała gama błędów powiązana z nieodpowiednim używaniem obiektowego stylu programowania, ale to już bardziej kwestia twórcy kodu niż samego TypeScripta.

Instalacja NodeJs

Zacznijmy od instalacji NodeJS, czyli środowiska uruchomieniowego dla JavaScript po stronie serwera. Dzięki temu będziemy mogli uruchamiać kod JavaScript (skompilowany z TypeScript) bez konieczności angażowania przeglądarki. NodeJS możemy pobrać ze strony nodejs.org. Wraz z Node instaluje się npm. Jest to manager paczek (i nie tylko, jeśli znasz Jave to możesz o nim myśleć jak o czymś na kształt Mavena).

Instalacja Visual Studio Code

Kolejnym krokiem jest instalacja IDE bądź edytora kodu. My skorzystamy z Visual Studio Code. Moim zdaniem jest to najlepszy edytor do pracy z TypeScript. Za ten edytor również odpowiada ekipa z Microsoft, więc cokolwiek nowego pojawia się w TypeScript jest momentalnie wspierane w Visual Studio Code. Poza tym edytor ten jest bardzo prosty w opanowaniu podstawowych funkcjonalności, a gigantyczna ilość wtyczek pozwala na rozbudowanie jego możliwości. VSC pobieramy ze strony https://code.visualstudio.com/ .

Instalacja TypeScript

Po instalacji niezbędnych narzędzi stwórzmy katalog dla naszego projektu (w moim wypadku będzie on nosił nazwę HelloTS). Otwórzmy ten folder w edytorze kodu. Otwieramy menu File, a w nim wybieramy Open Folder:

Instalacja TypeScript

Następnie otwieramy wbudowaną w VSC linie komend. Z menu wybieramy opcje Terminal, a z niej New Terminal i u dołu edytora pojawi nam się nowe okno.

W terminalu wpisujemy komendę npm init -y, dzięki to której do naszego katalogu zostanie dodany plik package.json, który jest plikiem konfigurującym projekt dla npm (np. określa zewnętrzne zależności projektu).

TypeScript możemy zainstalować globalnie bądź lokalnie. Ja preferuję instalacje lokalną. Wówczas każdy projekt jest odizolowany od innych, nie współdzieli z nimi zależności na konkretną, globalną wersję TypeScript.

By zainstalować TS lokalnie używamy komendy npm i typescript --save-dev Wówczas zostanie utworzony katalog node_modules z plikami TypeScript, oraz zależność ta zostanie dodana do pliku package.json w sekcji devDependencies:

{
  "name": "HelloTS",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^3.5.2"
  }
}

Następnie warto odpalić komende npx tsc --init, która doda plik konfiguracyjny dla TypeScript dla naszego projektu. W pliku tym ustawiamy m.in. do jakiej wersji ECMAScript ma kompilować się plik TypeScript, jakie są obostrzenia dotyczące typów (strict mode). Są tam też dziesiątki innych możliwych ustawień, ale to materiał na inny wpis, na chwilę obecną starczy nam to, co mamy.

Dzięki użyciu komendy npx mamy pewność, że zostanie użyta lokalna (przebywająca w node_modules/.bin) wersja kompilatora TypeScript. Dodatkowo npx spowoduje, że jeśli nie mamy danej aplikacji zainstalowanej to zostanie ona ściągnięta. W przypadku, gdy nie mamy globalnie zainstalowanego TypeScript to używanie tej komendy jest nadmiarowe… ale moim zdaniem lepiej przyzwyczaić się do jej używania. Zresztą po ulepszeniach naszego środowiska pracy nie będzie konieczne ciągle jej podawanie, ale o tym później.

TypeScript wprowadzenie – Pierwszy kod

Teraz możemy w końcu coś napisać, zaczniemy tradycyjnie od Hello World. Tworzymy plik Hello.ts:

const hello : string = 'Hello World';
console.log(hello);

docelowo ten kawałek kodu wypisze nam Hello World na konsoli.

Kompilacja i uruchomienie

Teraz możemy skompilować nasz kod za pomocą komendy npx tsc w wyniku jej pojawi się plik Hello.js. Zawarty jest w nim kod JavaScript, jaki powstał po kompilacji pliku TypeScript

"use strict";
var hello = 'Hello World';
console.log(hello);

Odpalamy ten kod za pomocą komendy node Hello.js. Na konsoli pojawi nam się śliczne Hello World

Upgrade 1 – Watch mode

O ile powyższe jest już dobrze zestawionym procesem to możemy wprowadzić pewne automatyzacje. Pierwszą z nich będzie uruchomienie kompilatora TypeScript w trybie –watch, dzięki któremu sam będzie nasłuchiwał na zmiany w plikach .ts i automatycznie je kompilował. Wystarczy w konsoli wpisać komende npx tsc --watch i teraz, gdy zmienimy coś w naszym kodzie źródłowym kod zostanie automatycznie przekompilowany. Na przykład wpiszmy

const hello : string = 'Hello, Paweł';
console.log(hello);

i po zapisaniu zostaniemy na konsoli poinformowani o rekompilacji. Jeśli zajrzymy do pliku Hello.js zobaczymy, że jego zawartość się zmieniła.

Żeby go uruchomić będziemy musieli odpalić drugą konsolę. Klikamy w plusik po prawej stronie okienka z terminalem

Możemy przepinać się między okienkami z pomocą menu rozwijanego znajdującego się obok plusika.

Wybieramy drugi terminal, uruchamiamy node ./Hello.js i mamy zaktualizowany kod. Teraz wszelkie zmiany w plikach .ts będą automatycznie kompilowane. Jedna komenda mniej do uruchamiania

Upgrade 2 – Nodemon

Nodemon jest to aplikacja, dzięki której możemy stworzyć swoisty watch-mode dla pliku .js. Za każdym razem, gdy zmieni się zawartość pliku Hello.js zostanie on ponownie uruchomiony (automatycznie wykona się komenda node Hello.js).

Instalujemy nodemon i uruchamiamy za pomocą komend npm i nodemon -save-dev oraz npx nodemon ./Hello.js

(npm install jest namiarowe… bo npx instaluje za nas barkujące rzeczy, ale warto pokazywać pewne rzeczy by informacja się utrwaliła)

Na konsoli otrzymamy komunikat o uruchomieniu nodemon oraz napis Hello Paweł.

Zmieńmy teraz tekst w pliku Hello.ts na powrót na Hello World. Po zapisaniu najpierw automatycznie zostanie ten plik skompilowany na plik js dzięki watch-mode kompilatora TypeScript, a następnie nodemon automatycznie uruchomi zmieniony plik.

Upgrade 3 – Lepsza widoczność w Visual Studio Code

Jedyna niedogodność, jaka nam została to fakt, że musimy przepinać się między dwoma konsolami, by zobaczyć czy kompilacja się powiodła, a następnie efekt naszych prac. Możemy to zmienić.

Zamykamy drugi terminal (ctr+c, a następnie komenda exit). Wybieramy terminal pierwszy, a następnie możemy wybrać albo w menu na górze Terminal -> Split Terminal


Bądź też kliknąć ikonkę podzielonego terminala na prawo od plusika służącego do tworzenia nowych linii komend.

W ten sposób mamy dwa terminale otworzone w jednym okienku na dole edytora. W drugim odpalamy ponownie nodemon i cieszymy się pełną automatyzacją oraz mamy wszystko widoczne na pierwszy rzut oka, gdyby gdzieś w naszym programie pojawił się błąd. Czy to kompilacji, czy w działaniu.

Kod dostepny na GitHub.

TypeScript wprowadzenie – Podstawowe typy wbudowane

W programowaniu typ jest to informacja o danej, która pomaga kompilatorowi (bądź interpreterowi, w zależności od języka) zrozumieć co z tą konkretną daną może zrobić i jakie wartości przechowuje.

I tak możemy przypisać typ do zmiennej, do parametru funkcji bądź też do wartości przez daną funkcję zwracanej. W TypeScript ten pierwszy przypadek będzie wyglądał tak: let name : string Dzięki temu kompilator będzie wiedział, że do zmiennej name może przypisać tylko wartości tekstowe. Czyli najpierw mamy let (bądź const) i nazwe dla zmiennej – to znamy z JavaScript. Następnie mamy skłanie TypeScriptowa – dwukropek, po którym następuje nazwa typu dla danej zmiennej, w tym wypadku jest to string.

Po co komu typowanie w JavaScript?

JavaScript przez lata radził sobie doskonale bez sztywnego typowania, po co więc wprowadzać je teraz?

JS w ostatnich latach odnotował gigantyczny skok popularności przez co powstają w nim coraz większe projekty, a nie tylko animacje spadających płatków śniegu na stronach internetowych. W dużych projektach bardzo ważną rzeczą jest utrzymanie kodu i jego czytelność. Typowanie w gigantycznym stopniu poprawiają obie te rzeczy. Podobnie jak możliwość bezpiecznego refaktoringu kodu. Dodatkowo już sama obecność typów dokumentuje dany program, bo informuję gdzie jakie wartości są oczekiwane.

Podstawowe typy wbudowane w TypeScript

TypeScript posiada kilka typów podstawowych, nas jednak na chwilę obecną interesują tylko trzy zupełnie podstawowe, mianowicie boolean, string i number.

Pierwszy z nich – boolean – jest to tak zwany typ logiczny, może przybierać dwie wartości – true bądź false.

let flag : boolean = false;
let isSet : boolean = true;

Kolejnym ze wspominanych typów jest string, czyli typ dla wartości tekstowych. Wartości tekstowe w TypeScript, podobnie jak w JavaScript możemy umieszczać pomiędzy różnymi znacznikami.

let firstName : string = 'Paweł';
let lastName : string = "Ćwik";
let longText : string = `this is some
                          kind of long,
                           multiline text with
                            ${firstName} variables included`

I finalnie mamy number, czyli wartości liczbowe. TypeScript pozwala nam na zapisywanie ich od razu w formacie binarnym, ósemkowym czy szesnastkowym, ale szczerze mówiąc nie jest to jakaś super przydatna funkcjonalność. O wiele ciekawszy jest prosty fakt możliwości zapisu dużych liczb żywając _ by oddzielić kolejne miejsca dziesiętne w celu poprawy czytelności.

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let bigOne: number = 21_322_101_32.2;

Pozorność typów

Po przekompilowaniu powyższych przykładów na JavaScript (ES2017) otrzymujemy co następuje:

"use strict";
let flag = false;
let isSet = true;
let firstName = 'Paweł';
let lastName = "Ćwik";
let longText = `this is some
                          kind of long,
                           multiline text with
                            ${firstName} variables included`;
let decimal = 6;
let hex = 0xf00d;
let binary = 0b1010;
let octal = 0o744;
let bigOne = 2132210132.2;

Czyli informację o typach zniknęły. W TypeScript typy obecne są tylko w statycznej części kodu – podczas jego pisania i kompilowania. Po kompilacji otrzymujemy wynikowy kod JavaScript pozbawiony wszelkich namiastek typów. Z jednej strony to dobrze – nie spowalniamy (choć są to rzędy nanosekund albo i mniejsze) przeglądarki sprawdzaniem typów za każdym razem, gdy przypisywana jest zmienna do wartości. Z drugiej strony tracimy pewność czy to, co otrzymaliśmy jako zwrotkę z serwisu REST faktycznie tym, czego oczekujemy.

Dedukcja typów przez TypeScript

TypeScript jest w stanie, dzięki kompilatorowi i serwerowi języka, który prześle odpowiednie informację do IDE/edytora kodu, wydedukować jaki typ ma miec dana zmienne. Gdy napiszemy coś takiego

let age = 27;

to po najechaniu na te linijke kursorem czy to w Visual Studio Code czy też na playgroundzie dostaniemy informacje w chmurcę

let age : number

Oznacza to tyle, że TypeScript wydedukował na podstawie wartości, że age ma być typu number;

TypeScript radzi sobie z dedukcją nawet dla tych bardziej skomplikowanych typów, które sami stworzymy. Jednak moim zdaniem na początku naszej przygody z TS powinniśmy zawsze pisać typy wprost, nieważne jak zdaniem niektórych nadmiarowe może to być. Chodzi o to, by przyzwyczajać się do używania typów oraz by uniknąć błędów spowodowanych faktem, że Typescript coś źle wydedukował.

Interesuje Cię TypeScript? Wprowadzenie do tego języka jest jednym z darmowych kursów dostępnych na kursy.clockworkjava.pl.

Aliasy typów w TypeScript

W poprzednim wpisie pokazałem trzy podstawowe typy wbudowane w TypeScript. W tym natomiast pokaże jak tworzyć własne. W TypeScript możemy je definiować za pomocą aliasów, interfejsów oraz klas. W tym wpisie zajmiemy się pierwszym sposobem i stworzymy alias typu – czyli zestawy mniej bądź bardziej konkretnych wartości, jakie może zmienna danego typu przyjmować.

Składnia dla alias typu

Typy w TypeScripts określają zbiór wartości jakie może przyjąć dana zmienna. Czyli na przykładlet x : number mówi nam, że do x możemy przypisać dowolną* liczbę.

Co w przypadku gdy chcemy by dana zmienna przybierała ograniczoną liczbę wartości?

Możemy na przykład zastosować aliasy typów wraz z operatorem | czyli OR (lub), znanego z wyrażeń warunkowych if/switch czy z wyrażeń logicznych.

By, na przykład, stworzyć typ, który będzie zawierał tylko wartości od 1 do 6 używamy składni:

type dice = 1 | 2 | 3 | 4 | 5 | 6; 
let x : dice = 5;

Słowo kluczowe type mówi nam, że tworzymy alias typu, następnie jest nazwa dla nowego typu, a po = następuje definicja tego typu.

Wracając do porównania typu jako 'worka’ z wartościami dla zmiennych. Stworzenie aliasu limited o definicji jak wyżej mówi nam, że wartości dla typu limited mogą być wzięte z worka gdzie jest wartość 1 lub worka z wartością 2 lub 3 i tak dalej. Każda z cyfr jest traktowana jak osobny typ.

Gdybyśmy chcieli stworzyć typ dla liter możemy zrobić type letter = 'a' | 'b' | 'c';

Ciekawsze aliasy

Oczywiście możemy mieszać różnie rodzaje typów podstawowych przy jednym alias typu. Nic nie stoi na przeszkodzie by stworzyć coś takiego:

type betterBoolean = 1 | true | 0 | false | 'Y' | 'N' | 'n' | 'y'; 
let a : betterBoolean = 1;
a = 'Y';
a = false;
a = 'TAK';

W ostatnim wypadku a = „TAK” dostaniemy błąd kompilacji TypeScript

Type 'TAK’ is not assignable to type 'betterBoolean’

Warto zwrócić uwagę na to, że „TAK” jest określane jako Type, a nie value.

Dodatkowo alias typu może przymować znane już nam typy podstawowe – number, string i boolean. Więc możemy zmienić nasz betterBoolean na coś takiego:

type betterBoolean = 1 | 0 | 'Y' | 'N' | 'n' | 'y' | boolean ; 

Czyli typ betterBoolean zawiera wszystkie wartości dla boolean oraz 1, 0 i literki.

Jako że po kompilacji informacje o typach znikają z pliku JavaScript, tak też po aliasach typów również ślad nie zostaje. Po kompilacji powyższy kod wygląda tak:

"use strict";
let a = 1;
a = 'Y';
a = false;
a = 'TAK';

TypeScript – wprowadzenie

TypeScript – wprowadzenie: Interfejsy w TypeScript

Typowanie obiektu

TypeScript pozwala oczywiście na dodawanie informacji o typie dla obiektu, na przykład kod

const me : { name: string, age: number } = {
  name: "Paweł",
  age: 33
}

utworzy stałą o nazwie me, która musi być obiektem o dwóch polach – pierwszy o nazwie name, typu tekstowego oraz age o typie number. Następnie do tak zdeklarowanej zmiennej przypisujemy już właściwy obiekt, spełniający powyższy typ.

O ile jednorazowe zdefiniowane niewielkiego typowania dla zmiennej nie jest większym problemem to gdy chciemy mieć klika zmiennych tego typu to zamiast pisać definicje typu za każdym razem

const me : { name: string, age: number } = {
  name: "Paweł",
  age: 33
}

const you : { name: string, age: number } = {
  name: "Tomek",
  age: 29
}

możemy zdefiniować interfejs.

Interfejsy w TypeScript

interface Person {
  name: string,
  age: number
}

const me : Person = {
  name: "Paweł",
  age: 33
}

const you : Person = {
  name: "Tomek",
  age: 29
}

Interfejsy tworzymy za pomocą słowa kluczowego interface, po którym następuje nazwa dla nowo utworzonego typu, a po niej otwieramy nowy blok nawiasami klamrowymi, by zdefiniować pola, jakie będą określały interfejs.

Kompozycja

Pola interfejsów mogą być typami podstawowymi, jak w powyższych przykładach. Nic nie stoi jednak na przeszkodzie by interfejs, posiadał pole o typie definiowanym przez inny interfejs.

interface Person {
  name: string,
  age: number,
  email: Email
}

const me : Person = {
  name: "Paweł",
  age: 33,
  email : { name: 'pawel', domain: 'gmail.com'}
}

const you : Person = {
  name: "Tomek",
  age: 29,
  email : { name: 'tomek', domain: 'gmail.com'}
}

interface Email {
    name: string,
    domain: string
}

Roszerzyliśmy nasz interfejs Person o dodatkowe pole email, której to również jest obiektem, definiowanym przez interfejs Email.

Warto również zauważyć, że nie musimy deklarować interfejsu zanim go użyjemy. Możemy to zrobić niżej, jak w przykładzie powyżej. Natomiast dobre praktyki programistyczne wymagają by jednak definicja interfejsu pojawiła się przed jej pierwszym użyciem.

Pola opcjonalne

TypeScript pozwala by pola definiujące interfejs były opcjonalne. Tj. obiekt, który chcemy przypisać do zmiennej o danym typie nie musi posiadać pól oznaczonych jako opcjonalne.

interface Email {
    name: string,
    domain: string
}

interface Person {
  name: string,
  age: number,
  email?: Email
}

const me : Person = {
  name: "Paweł",
  age: 33,
  email : { name: 'pawel', domain: 'gmail.com'}
}

const you : Person = {
  name: "Tomek",
  age: 29,
}

Pola opcjonalne oznaczamy używając ? przed przypisaniem typu. Jak wyżej email?: Email.

Dopasowanie interfejsów

Bardzo ważną rzeczą jest fakt, że by dany obiekt został dopasowany do interfejsu musi on posiadać dokładnie takie same pola wymagane (nieopcjonalne) jak te zadeklarowane w interfejsie. Nie może mieć ich ani mniej, ani więcej.

const she : Person = {
  name: "Marta",
  age: 29,
  gender: female
}

cons he : Person = {
  name: "Łukasz"
}

W dwóch powyższych przypadkach dostaniemy błąd kompilacji. Pierwszy obiekt (she) ma za dużo pól z nadmiarowym gender. Drugi natomiast (he) ma ich za mało, brakuje age.

Z dopasowywaniem interfejsów wiąże się jeszcze jeden, bardzo istotny fakt. Mianowicie interfejs to tylko nazwa nadana przez człowieka dla zestawu propertsów.

Mając dwa interfejsy

interface Pet {
   name: string,
   age: number
}

interface Person {
   name: string,
   age: number
}

To dla TypeScript te dwa interfejsy kryją pod sobą te same wartości, więc obiekty jednego mogą być spokojnie przypisywane do drugiego. Pomimo tego, że w głowie programisty Pet i Person to dwa, zupełnie rozdzielne byty. Poniższe skompiluje się bez problemu:

let pet : Pet;
let person : Person = { name: "Pawel", age: 33 }
pet = person;

Klasy w TypeScript

Ogólny opis klas można odszukać poza TypeScript wprowadzenie, w tym poście – po nieco teoretycznym spojrzeniu na klasy i obiekty, pora przyjrzeć im się bliżej w TypeScript.

Definicja klasy w TypeScript

To jak wygląda podstawowa definicja klasy w TypeScript niewiele różni się od tego jak wygląda to w JavaScript ES6+

class House {

   owner : string;

   constructor(owner : string) {
      this.owner = owner;
   }

   changeOwner(owner : string) : void {
      this.owner = owner;
   }

   getOwner() : string {
     return this.owner;
   }

}

Zaczynamy od słowa kluczowego class, po którym następuje podanie nazwy dla nowej klasy oraz otwarcie nawiasu klamrowego.

Następnie mamy jedną różnicę. W dodatku taką, która powoduję, że przeklejenie kodu klasy z ES6+ powoduje krzyk i płacz kompilatora. Mianowicie potrzebujemy deklaracji pól, jakie zawiera dana klasa, wraz z typem danych. Jeśli nie podamy będzie niejawnie przypisany typ any. W naszym wypadku jest to stan owner o typie tekstowym string.

Kolejnym elementem w kolejności jest konstruktor, czyli specjalna metoda, która jest wywoływana przy tworzeniu nowych obiektów, na podstawie danej klasy. W TypeScript konstruktory definiujemy za pomocą słówka kluczowego constructor i podania parametrów dla funkcji i logiki biznesowej.

Pamiętajmy, że w konstruktorach, niezależnie od języka programowania, logika powinna być ograniczona do minimum i najlepiej ograniczona do inicjalizacji wartości pól.

Finalnie mamy dwie metody – changeOwner i getOwner. Deklaracja funkcji klasy niczym nie różni się od tej znanej z JavaScript. Podajemy nazwę dla funkcji i listę parametrów.

Finalnie zamykamy nawias klamrowy.

Tworzenie nowych obiektów

Nowe obiekty w TS tworzymy tak samo jak w dziesiątkach innych języków – za pomocą słowa kluczowego new.

const house : House = new House("Pawel"); 

W kontekście TypeScript ważne jest, że każda klasa definiuje nowy typ. Tak jak wyżej – mamy zmienną o typie wyprowadzonym z klasy – House.

Na podstawowym poziomie więc jedyne czego wymaga od nas TypeScript ponad to, co znamy z JavaScript to podanie stanów, jakie posiada owa klasa. Najlepiej wraz z typami, już na początku jej deklaracji.

Dzięki temu od razu wiemy jakie dane przechowywuję klasa, nie musimy analizować całego kodu w poszukiwaniu this.<nazwa stanu>.

Modyfikatory dostępu


Zapraszam do dołączenia za darmo do kilkugodzinnego kursu wprowadzającego do TypeScript.


Modyfikatory dostępu w TypeScript – rodzaje

TypeScript wspiera trzy typy modyfikatorów dostępu – public, private i protected. Używamy ich przed nazwami zmiennych bądź funkcji.

class Info {
    
  private name: string;
  private email: string;
  private preferences: string[];

  constructor(name: string, email: string) {
      this.name = name;
      this.email = email;
      this.preferences = [];
  }

  public toString = () => {
     return `${this.name} - ${this.email}`;
  };
}

Domyślnym z nich jest public, więc linijka

constructor(name: string, email: string)

tłumaczy się na

public constructor(name: string, email: string)

Public

Public jest najszerszym modyfikatorem dostępu, umożliwia on dostęp do tak oznaczonego elementu wszystkim i wszystkiemu. Jest to, jak już wspominałem, domyślny poziom dostępu. Jest to też jedyny poziom dostępu w JavaScript, gdzie wszystko jest publiczne.

Poniższy kod wykona się więć bez żadnego problemu:

class Info {
    
  public name: string;
  public email: string;
  public preferences: string[];

  constructor(name: string, email: string) {
      this.name = name;
      this.email = email;
      this.preferences = [];
  }

  public toString = () => {
    return `${this.name} - ${this.email}`;
  };
}

class ClassifiedInfo extends Info {
  public toString = () => {
    return `${super.name}. Everything else is classified`;
  }
}

let info : Info = new Info("Pawel", "pawel@pawel");
console.log(info.name);
info.email = "nowy@gmail";
console.log(info.toString());

Do zmiennych email, name oraz metody toString jest dostęp z poziomu klasy definiującej te pola (Info), jak i klasy rozszerzającej klasę Info (ClassifiedInfo) oraz dodatkowo z globalnego scope aplikacji.

Protected

Modyfikator protected ucina nieco swawole wyczyniające się dzięki public. Do elementów tak oznaczonych ma dostęp tylko klasa, w której zostały one zdefiniowane oraz wszelkie klasy dziedziczące po niej.

class Info {
    
  protected name: string;
  protected email: string;
  protected preferences: string[];

  constructor(name: string, email: string) {
      this.name = name;
      this.email = email;
      this.preferences = [];
  }

  protected toString = () => {
    return `${this.name} - ${this.email}`;
  };
}

class ClassifiedInfo extends Info {
  public toString = () => {
    return `${super.name}. Everything else is classified`;
  }
}

let info : Info = new Info("Pawel", "pawel@pawel");
console.log(info.name);
info.email = "nowy@gmail";
console.log(info.toString());

Dostęp do pól z poziomu klasy Info i ClassifiedInfo jest wciąż dozwolony, ponieważ ClassifiedInfo jest dzieckiem klasy Info. Natomiast wywołania w przestrzeni globalnej wywołają błędy kompilatora.

Private

Private jest ostatnim z modyfikatorów dostępu w TypeScript i jest niemal lustrzanym odbiciem public. Do elementów określonych jako private dostęp jest tylko z poziomu klasy, która je deklaruję. Natomiast dostęp z klas potomnych czy przestrzeni globalnej wywoła płacz kompilatora.

class Info {
    
  private name: string;
  private email: string;
  private preferences: string[];

  constructor(name: string, email: string) {
      this.name = name;
      this.email = email;
      this.preferences = [];
  }

  private toString = () => {
    return `${this.name} - ${this.email}`;
  };
}

class ClassifiedInfo extends Info {
  public toString = () => {
    return `${super.name}. Everything else is classified`;
  }
}

let info : Info = new Info("Pawel", "pawel@pawel");
console.log(info.name);
info.email = "nowy@gmail";
console.log(info.toString());

Przy tak określonym dostępie zarówno w przestrzeni globalnej, jak i w klasie ClassifiedInfo pojawią się błędy kompilacji, w klasie Info nadal wszystko będzie w porządku.

Jak używać modyfikatorów dostępu

Oczywiście robienie wszystkiego prywatnym bądź publicznym to nie jest rozwiązanie.

Najprostszym podejściem do problemu „jakiego modyfikatora dostępu użyć” jest następujący algorytm:

  1. Stan klasy zawsze jest prywatny.
  2. Konstruktor jest publiczny.
  3. Metody używane przez inne klasy są publiczne, natomiast wszelkie metody pomocnicze (tzn. takie używane przez implementacje metody publicznej) są prywatne.
  4. Jeśli musimy wystawić na zewnątrz możliwość pobrania/zmiany stanu robimy to poprzez getter/setter
class DataTransformer {

    private data: string;
    private reversed: string[];
    private transformed: string;

    constructor(data: string) {
       this.data = data;
       this.reversed = [];
       this.transformed = "";
    }

    private splitData() : void {
       this.reversed = this.data.split("");
    }

    private reverseData() : void {
       this.reversed = this.reversed.reverse();
    }

    private joinData() : void {
       this.transformed = this.reversed.join("");       
    }

    public transform() : string {
       this.splitData(); 
       this.reverseData();
       this.joinData();
       return this.transformed;  
    }
}

const trans : DataTransformer = new DataTransformer("hello");
console.log(trans.transform());

Oczywiście jak do wszystkiego jest masa wyjątków od tego prostego podejścia, ale na start jest w zupełności wystarczające.

Runtime

W trakcie działania aplikacji mamy do czynienia z JavaScript, więc niestety modyfikatory dostępu giną i wszystko jest publiczne.

TypeScript w praktyce, na niepraktycznym przykładzie.

W ostatniej części cyklu wprowadzającego do Typescript weźmiemy trochę rzeczy, o których traktowały poprzednie wpisy i zaimplementujemy prostą aplikację internetową.

I to właśnie sformułowanie „prostą aplikację internetową” czyni ten przykład… niepraktycznym. Mianowicie im projekt większy, tym więcej daje nam TypeScript z jego silnym typowaniem, generykami, interfejsami. Większość z tych rzeczy jest nadmiarowa przy prostych aplikacjach webowych, które spokojnie można tworzyć w JavaScript bez obawy jakiś drastycznych pomyłek czy zamotaną architekturę.

Stworzenie projektu

Tyle słowem wstępu, weźmy się do pracy. Naszym celem jest stworzenie prostej listy zadań do wykonania. Będą one przechowywane tylko na poziomie sesji, więc nie będziemy myśleć na chwilę obecną o persystencji danych.

Zaczniemy od utworzenia projektu – czyli komend

npm init
npm install typescript --save-dev
npx tsc --init

oraz utworzeniu nowego projektu w Visual Studio Code

Szkielet HTML

Tworzymy plik index.html, a w nim używamy skrótu html:5, by stworzyć szkielet strony. Co będzie nam potrzebne to input box do wprowadzenia rzeczy, którą mamy do zrobienia, oraz miejsca na listę.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>To Do</title>
  </head>
  <body>
    <main>
      <section>
        <form>
          <input
            type="text"
            id="todo-desc"
            placeholder="Co masz do zrobienia..."
          />
          <button id="todo-sub">Dodaj</button>
        </form>
      </section>
      <section>
        To do:
        <ul id="todo-list"></ul>
      </section>
    </main>
  </body>
</html>

Dodanie TypeScript do HTML

Stwórzmy plik App.ts, gdzie narazie dodamy tylko „hello world”

console.log("Hello World");

Gdybyśmy chcieli być koszerni to stworzylibyśmy skrypt TypeScript, następnie skompilowali i wynikowy plik .js dodali dopiero jako zależność w tagu <scripts>.

Na szczęście możemy mocno uprościć sobie życie korzystając z bardzo fajnego bundlera o nazwie Parcel

npm install parcel-bundler --save-dev

Teraz na końcu pliku index.html możemy odnieść się bezpośrednio do pliku App.ts

</main>
    <script src="App.ts"></script>
  </body>
</html>

i uruchomić nasz projekt za pomocą komendy

npx parcel index.html

Logika biznesowa w TypeScript

W pierwszej kolejności zaimplementujemy klase odpowiedzialną za wpisy ToDo. Będzie ona posiadała dwa pola – description czyli opis oraz pole typu boolean mówiące czy dany element został już wykonany.

export class ToDo {
  description: string;
  done: boolean;

  constructor(description: string) {
    this.description = description;
    this.done = false;
  }

  complete(): void {
    this.done = true;
  }
}

Drugą klasą będzie ToDoRepository – czyli miejsce gdzie trzymane będą wszystkie wpisy ToDo.

import { ToDo } from "./ToDo";

export class ToDoRepository {
  private todos: ToDo[] = [];

  public addToDo = (description: string, uiNode: HTMLElement): void => {
    console.log(`Added ${description} to ${JSON.stringify(this.todos)}`);
    const toDo = new ToDo(description);
    this.todos.push(toDo);
  };
}

Finalnie dodamy App.ts, odpowiedzialne za podpięcie się do stosownych elementów HTML, dodanie listenerów.

import { ToDoRepository } from "./ToDoRepository";

const repo = new ToDoRepository();

const addButton: HTMLElement | null = document.getElementById("todo-sub");
const inputElement: HTMLElement | null = document.getElementById("todo-desc");
const todoList: HTMLElement | null = document.getElementById("todo-list");

if (addButton !== null && inputElement !== null && todoList !== null) {
  addButton.addEventListener("click", (e: Event) => {
    if (inputElement instanceof HTMLInputElement) {
      e.preventDefault();
      const newItem: HTMLElement = document.createElement("li");
      repo.addToDo(inputElement.value);

      todoList.appendChild(newItem);
    }
  });
}

Za pomocą komendy npx parcel .\src\index.html odpalamy naszą aplikację i powinniśmy mieć już możliwość dodawania nowych ToDo.

I na tym etapie możemy zakończyć ten prosty przykład, który będzie stanowił doskonałą bazę do dalszej przygody z Typescript i ReactJS.

I tu zakonczymy TypeScrypt wprowadzenie.

Interesuje Cię TypeScript? Wprowadzenie do tego języka jest jednym z darmowych kursów dostępnych na kursy.clockworkjava.pl.