Introducere în TypeScript
Vom face o scurtă prezentatare a limbajului TypeScript pentru a dezvolta aplicații de frontend. Modul de lucru cu Typescript este în mare același ca cu JavaScript dar beneficiază de faptul că sintaxa este constrânsă de tipurile de date declarate. Existența tipurilor de date, chiar dacă în acest caz sunt verificate doar la faza de transpilare (transpiling) unde codul de TypeScript este transformat în cod normal de JavaScript, ajută dezvoltatorii în a omite greșeli frecvente în limbaje slab tipate. Vom trece prin sintaxa specifică de TypeScript, pentru cei familiarizați cu limbaje C-like și orientate pe obiect sintaxa este ușor de învașat însă diferă în modul de utilizare față de aceste limbaje.
Variabile și constante
În JavaScript/TypeScript pentru a declara varibile și constante se folosesc cuvintele cheie let respectiv const, diferenșa fiind că variabilele declarate cu let pot fi reasignate. Există și cuvantul cheie var însă folosirea acestuia este dezcurajată deoarece variabilele declarate în acest mod există într-un scop global în interiorul programului și nu intr-un scop local, lucru ce conduce la probleme grave de securitate.
const constant = 5;
let value = 10;
value += constant;
console.log("Assigned value is: ", value);
// output - Assigned value is: 15
Tipuri
Tipurile cu care se lucrează în TypeScript sunt:
- number - valori numerice care pot fi numere întregi, numere în virgula mobilă sau NAN (Not A Number)
- string - șiruri de caractere declarate fie între "" sau între ''
- boolean - valori booleene cu singurele valori true și false
- array - vectori de orice tip (string[], number[], boolean[] etc) folosind [], acestea vin cu funcții clasice map, flatMap, reduce etc.
- null - valorile null reprezintă aici propriul tip ca să indice ca valoarea poate fi nulă
- undefined - este un tip special pentru a indica inexistența unui camp într-un obiect, parametru de funcție sau definerea unei variabile
- object - se declara folosind în care se pot adauga campuri cu nume și valorile aferente
- function - în JavaScript/TypeScript se pot declara atât funcții normale ca tip de date cât și functii lambda (numite și arrow functions)
- any - un tip nesigur care poate fi asignat la orice și să i se asigneze orice valoare
- unknown - este varianta mai sigură de la any, i se poate asigna orice valoare dar se poate asigna doar la o alta variabilă unknown
- never - este un tip fără valori la care se poate asigna orice valoare, este folosit, de exemplu, pentru a semnala ca o funcție nu returneaza niciodată și că aruncă excepție
- void - este folosit pentru a arata ca o funcție nu returnează
let booleanValue = true; // tipul variabilei este deperminat implicit ca boolean
let numericValue: number = 5; // tipul variabilei este declarată explicit
let maybeString = booleanValue ? "Hello" : null; // tipul variabilei va fi implicit string | null
let objectValue = { // declarăm un tip anonim { stringValue: string | null, length: number | undefined }
stringValue: maybeString, // implicit tipul acestui câmp va fi tipul variabilei
length: maybeString?.length // putem folosi operatorul ?. pentru a întoarce undefined dacă maybeString nu are o valuoare, tipul campului va fi number | undefined
}
let array: number[] = [1, 2, 3]; // implicit acest vector va contine valori de tipul valorilor declarate aici, dacă nu se declară explicit tipul atunci acest vector ar fi un tuplu [number, number, number]
function isOdd(value: number) { // declăram o funcție de tip (value: number) => boolean
return number % 2 === 0;
}
const isOddVariable: (value: number) => boolean = isOdd;
const isEven = (value: number): boolean => { return number % 2 === 0; }; // se asignează un lambda de tipul (value: number) => boolean
let isOddResult = isOddVariable(numericValue);
let isEvenResult = isEven(numericValue); // putem apela un lambda ca orice funcție normală
Declararea de noi tipuri
Am văzut cum putem defini tipuri de obiecte în mod anonim dar pentru o organizare a codului mai bună putem declara tipuri complexe folosind cuvintele cheie type și interface astfel:
type Point2D = { // declarăm un alias pentru acest tip de obiect
x: number
y: number
test: () => boolean
}
interface Point2D { // declăram o interfața de această formă
x: number
y: number
test: () => boolean
}
În principiu, atât aliasurile de tip cât și interfețele pot fi folosite pentru a declara "forma" unui obiect însă sunt cateva diferențe aici. Aliasurile de tip față de interfețe pot fi folosite pentru alte tipuri decât obiecte cum ar fi uniuni.
type ValueStringUnion = "value1" | "value2" | "value3"; // acest tip reprezintă șiruri de caractere doar cu aceste trei posibilități
type StringOrStringGet = string | () => string; // declarăm un tip care poate fi un șir de caractere sau o funcție care returneaza un șir
type StringAndStringSet = [string, (value: string) => void]; // declarăm un tip tuplu care conține un șir de caractere și o funcție care folosește un șir
Atât aliasurile de tip cât și interfețele pot fi extinse.
interface Point2D { x: number; y: number; }
interface Point3D extends Point2D { z: number; } // o interfață poate extinde altă interfață
type Point2D = { x: number; y: number; }
type Point3D = Point2D & { z: number; } // la fel și pentru alias de tip
interface Point2D { x: number; y: number; }
type Point3D = Point2D & { z: number; } // un alias de tip poate extinde și o interfață
type Point2D = { x: number; y: number; }
interface Point3D extends Point2D { z: number; } // la fel și interfața poate extinde un alias dacă nu este o uniune
Interfetele fata de aliasuri de tip pot fi imbinate ca de exemplu:
interface Point2D { x: number; }
interface Point2D { y: number; }
const point: Point2D = { x: 0, y: 0 }; // nu dă eroare, interfața finală va fi combinată din ambele declarări
Trebuie să mentionăm aici că indiferent dacă vorbim de alias de tip sau interfață, tipurile vor fi verificate și trebuie sa respecte constrangerile de tip când sunt folosite, de exemplu, într-o funcție sau la asignare de variabile. De exemplu, dacă o funcție are ca parametru de tip string | number atunci poate primi ca parametru o variabilă de acest tip sau de tip string sau nubmer pentru că ambele sunt subtipuri ale acestei uniuni. Dacă în schimb tipul este { name: string } & { value: number } atunci în mod necesar valoarea primită trebuie să fie de forma { name: string, value: number } sau ceva ce extinde acest tip.
Clase
La fel ca în limbajele orientate pe obiect în JavaScript/TypeScript există și support pentru declararea claselor dar în general nu sunt atât de frecvent folosite. Vom mentiona doar cum se pot declara și folosi:
class PointImplementation implements Point2D { // poate implementa atât o interfață cât și un alias de tip
private name: string;
public x = 0;
public y = 0;
public constructor(name: string) {
this.name = name;
}
public getName(): string {
return this.name;
}
}
const point = new PointImplementation("NewPoint");