Základy TypeScriptu
Z předchozí kapitoly už víme, jak TypeScript nainstalovat a jak přeložit TypeScriptový kód do JavaScriptu. Teď si popíšeme základní myšlenky a principy TypeScriptu.
TypeScript je nástroj, který analyzuje váš kód a hledá v něm chyby. Používá k tomu informace ze dvou zdrojů: buď ty, které mu poskytnete formou typových anotací, nebo ty informace, které je schopný odvodit přímo ze zdrojového kódu. Podíváme se nejprve na druhý způsob.
Odvození typů ze zdrojového kódu
Vezměme si například tento kód:
const text = "Hello World!";
console.log(text.toUpperCase());
console.log(text.reverse());
Vidíme, že je to obyčejný Javascriptový kód, který zdánlivě nemá nic společného s TypeScriptem. Nejprve definujeme konstantu text a poté vypíšeme text velkými písmeny a následně obrácený text. Spustíme-li kód přes NodeJS jako obyčejný JavaScript, dostaneme tento výsledek:
> node text.js
HELLO WORLD!
/Users/lukashavrlant/Projects/temp/text.js:3
console.log(text.reverse());
^
TypeError: text.reverse is not a function
Ouha, metoda reverse totiž není metodou stringu, ale metodou pole. Program nám tedy spadnul, protože jsme zavolali metodu, která neexistuje. Zároveň si můžeme všimnout, že kód nám vypsal text “HELLO WORLD!”, protože NodeJS vykonával kód řádek po řádku – druhý řádek byl ještě v pořádku, takže ho provedl, chyba byla až na třetím řádku. Co by se stalo, když bychom stejný kód zkusili přeložit pomocí TypeScriptu?
> npx tsc text.ts
text.ts:3:18 - error TS2339: Property 'reverse' does not exist on type '"Hello World!"'.
3 console.log(text.reverse());
~~~~~~~
Překlad js souborů
TypeScript nemá úplně rád, když se pomocí něj snažíte přeložit *.js soubor. Pokusíte-li se o to, zahlásí chybu error TS6504: File 'text.js' is a JavaScript file. Nejjednodušší způsob jak se chybě vyhnout je přejmenovat soubor na *.ts. Proto jsme v předchozí ukázce volali npx tsc text.ts. Pokud si nechcete hrát s příkazovou řádkou, můžete si kód vyzkoušet na typescriptlang.org/play.
Vidíme, že přestože jsme TypeScriptu předhodili čistě JavaScriptový kód bez jediné změny, stejně v něm dokázal najít chybu a upozornil nás na ni ještě předtím, než jsme daný kód reálně spustili. Tohle platí obecně:
Každý validní JavaScriptový kód je zároveň validním TypeScriptovým kódem.
Výhody TypeScriptu tak můžeme začít čerpat, aniž bychom museli měnit náš JavaScriptový kód, protože si můžeme zanalyzovat čistě JavaScriptový kód TypeScriptím překladačem. Pokud chceme kód JavaScriptový jen analyzovat a nechceme ho “překládat” do JavaScriptu, když už v JavaScriptu je, můžeme k tomu použít přepínač --noEmit, který zařídí to, že TypeScript kód zkontroluje, ale nebude generovat JavaScriptové soubory.
> npx tsc text.ts --noEmit
TypeScript obsahuje definice pro všechny funkce a metody ze standardní knihovny, proto ať použijete jakoukoliv takovou funkci, TypeScript za vás ohlídá, že ji voláte správně – alespoň se správnými typy. Všechny následující příklady jsou chybé a TypeScript vás na to upozorní:
// Argument of type 'number' is not assignable to parameter of type 'string'
parseFloat(1.23)
// The right-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type
1 / []
// Type '123' must have a '[Symbol.iterator]()' method that returns an iterator
for (const item of 123) {}
// Type 'String' has no construct signatures.
const ClassConstructor = "Class"
new ClassConstructor()
Typové anotace
Jsou chvíle, kdy TypeScript dokáže odvodit, jakého typu daná proměnná je, ale častěji to neví. Vezměme si pro příklad tuto funkci:
function reverseText(text) {
return text.split('').reverse().join('');
}
Přestože je nám asi jasné, že proměnná text má být string, tak TypeScript to nemá jak spolehlivě odvodit. Možná máme v našem programu nějakou úplně jinou třídu A, která má metodu split, která vrací instanci třídy B, jejíž metoda reverse vrací instanci třídy C a podobně. Aby TypeScript nemusel hádat, musíme mu pomoci typovými anotacemi. Zkrátka o daném parametru funkce řekneme, jakého má být typu. V TypeScriptu to děláme tak, že za název parametru nebo proměnné napíšeme středník následovaným daným typem:
function reverseText(text: string) {
return text.split('').reverse().join('');
}
V tuhle chvíli už TypeScript na 100 % ví, že parametr text je typu string a je schopen hlídat správné použití. Pokud bychom například upravili kód takto
function reverseText(text: string) {
return text.reverse().join('');
}
tak nám TypeScript zahlásí chybu o neexistující metodě reverse: Property 'reverse' does not exist on type 'string'. TypeScript zvládne odvodit i návratový typ funkce. Pokud použijeme funkci reverseText, TypeScript bude chápat, že funkce vrátila zase string a bude dále hlídat jeho správné použití. Příklad:
const reversed = reverseText("Alan Turing");
reversed.sort(); // Property 'sort' does not exist on type 'string'
function reverseText(text: string) {
return text.split('').reverse().join('');
}
TypeScript nás upozorní, že nemůžeme na řádku dva zavolat metodu sort, protože proměnná reversed je typu string a ten takovou metodu nemá. Přestože je TypeScript schopný sám odvodit návratový typ funkcí, je dobré explicitně návratový typ definovat. To se dělá zase pomocí dvojtečky za seznamem parametrů takto:
function reverseText(text: string): string {
return text.split('').reverse().join('');
}
Nebudeme-li definovat návratový typ funkce ručně, ušetříme sice nějaký čas, ale vystavujeme se riziku, že omylem upravíme funkci a změníme návratový typ, aniž bychom to chtěli. Můžeme třeba přidat do funkce nešikovnou podmínku:
function reverseText(text: string) {
if (text.length > 1) {
return text.split('').reverse().join('');
}
}
a funkce najednou nevrací string, ale vrací string nebo undefined v případě, že délka text je menší než dva. Definujeme-li návratový typ ručně a explicitně, nestane se nám, že při nějaké pozdější úpravě začne funkce omylem vrací jiný typ, než očekáváme. Explicitním zadáním návratového typu funkce křičíme do světa “tahle funkce vrací string”.