Gărzi de tip în TypeScript
O gardă de tip este o verificare care restrânge tipul unei variabile în interiorul unui bloc de cod. După verificare, TypeScript cunoaște tipul mai precis și îți permite să îl folosești în siguranță.
Cum funcționează gărzile de tip în TypeScript
Gărzile de tip sunt verificări explicite care stabilesc tipul exact al unei valori în timpul execuției. TypeScript citește aceste verificări și restrânge tipul în codul care urmează.
De ce să folosești gărzi de tip?
- Siguranța tipurilor: operațiile se execută doar pe valori de tipul potrivit
- Claritatea codului: verificarea tipului este explicită și se documentează singură
- Unelte mai bune: editorul oferă completare corectă pentru tipul restrâns
- Prevenirea erorilor: greșelile de tip sunt prinse la compilare
- Siguranță la execuție: verificarea protejează codul și în timp ce rulează
Tipare de gărzi de tip prezentate pe această pagină:
- gărzi de tip cu
typeof - gărzi de tip cu
instanceof - gărzi de tip definite de utilizator, cu predicate de tip
- reuniuni discriminate cu tipuri literale
- gărzi de tip cu operatorul
in - funcții de aserțiune de tip
Gărzi de tip cu typeof
Operatorul typeof este o gardă de tip inclusă în limbaj. El verifică tipul unei valori primitive în timpul execuției.
Este cel mai util pentru restrângerea reuniunilor de primitive precum string, number și boolean.
Utilizare de bază
În interiorul ramurii if (typeof value === 'string'), TypeScript tratează value ca string. În ramura else este un number. Fiecare ramură poate apela metodele tipului său.
typeof care restrânge un parametru string | number.
Scrie o funcție formatValue(value: string | number): string care, pe ramura de string, elimină spațiile și transformă valoarea în majuscule, iar pe ramura de number apelează toFixed(2), apoi apeleaz-o cu ' salut ' și 42.1234, păstrând rezultatele în result1 și result2, cu comentarii care arată "SALUT" și "42.12". Exemplul nu afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// Gardă de tip simplă cu typeof
function formatValue(value: string | number): string {
if (typeof value === 'string') {
// Aici TypeScript știe că value este string
return value.trim().toUpperCase();
} else {
// Aici TypeScript știe că value este number
return value.toFixed(2);
}
}
// Exemplu de utilizare
const result1 = formatValue(' salut '); // "SALUT"
const result2 = formatValue(42.1234); // "42.12"
Gărzi de tip cu instanceof
Operatorul instanceof verifică dacă un obiect a fost creat de o anumită clasă sau funcție constructor.
Folosește-l pentru a restrânge reuniuni de instanțe de clase sau de obiecte incluse în limbaj.
Restrângerea tipului pe bază de clasă
Verificarea animal instanceof Bird restrânge animal la Bird în prima ramură, așa că fly() este permis. În ramura else valoarea trebuie să fie un Fish.
instanceof care restrânge o reuniune de două clase.
Declară o clasă Bird cu o metodă fly() care afișează Zboară... și o clasă Fish cu o metodă swim() care afișează Înoată..., apoi o funcție move(animal: Bird | Fish) care apelează fly() când animal instanceof Bird și swim() în caz contrar. Exemplul doar definește funcția și nu afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
class Bird {
fly() {
console.log("Zboară...");
}
}
class Fish {
swim() {
console.log("Înoată...");
}
}
function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
// Aici TypeScript știe că animal este Bird
animal.fly();
} else {
// Aici TypeScript știe că animal este Fish
animal.swim();
}
}
Gărzi de tip definite de utilizator
Pentru verificări mai complexe poți scrie propria funcție gardă de tip. Tipul ei de retur este un predicat de tip, de forma parameterName is Type.
Funcții cu predicat de tip
Semnătura vehicle is Car îi spune lui TypeScript: când această funcție returnează true, tratează vehicle ca pe un Car. În ramura else este restrâns la Motorcycle.
Car (make, model, year, plus un type?: undefined opțional, ca să nu poată fi confundată cu o motocicletă) și Motorcycle (aceleași câmpuri plus type: "sport" | "cruiser"), o funcție isCar(vehicle: Car | Motorcycle): vehicle is Car care verifică dacă proprietatea type este undefined, și o funcție displayVehicleInfo care afișează marca, modelul și anul, apoi afișează Acesta este un automobil sau Aceasta este o motocicletă <type>, în funcție de gardă. Doar definiții, fără apeluri, așa că nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
interface Car {
make: string;
model: string;
year: number;
type?: undefined; // o mașină nu are niciodată tipul unei motociclete, așa că cele două forme rămân distincte
}
interface Motorcycle {
make: string;
model: string;
year: number;
type: "sport" | "cruiser";
}
// Funcție cu predicat de tip
function isCar(vehicle: Car | Motorcycle): vehicle is Car {
return (vehicle as Motorcycle).type === undefined;
}
function displayVehicleInfo(vehicle: Car | Motorcycle) {
console.log(`Marcă: ${vehicle.make}, Model: ${vehicle.model}, An: ${vehicle.year}`);
if (isCar(vehicle)) {
// Aici TypeScript știe că vehicle este Car
console.log("Acesta este un automobil");
} else {
// Aici TypeScript știe că vehicle este Motorcycle
console.log(`Aceasta este o motocicletă ${vehicle.type}`);
}
}
Reuniuni discriminate
O reuniune discriminată (numită și reuniune etichetată) este o reuniune de tipuri obiect care au în comun o proprietate, discriminantul. Valoarea lui literală deosebește tipurile între ele.
Acest tipar funcționează foarte bine împreună cu gărzile de tip.
Reuniune discriminată de bază
Proprietatea comună kind este discriminantul. Un switch pe shape.kind restrânge shape la Circle sau Square în fiecare caz.
switch pe discriminant.
Declară interfețele Circle (kind: "circle", radius) și Square (kind: "square", sideLength), o reuniune Shape și o funcție calculateArea(shape: Shape) care face switch pe shape.kind și returnează Math.PI * radius ** 2 pentru cercuri și sideLength ** 2 pentru pătrate. Doar definiții, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function calculateArea(shape: Shape) {
switch (shape.kind) {
case "circle":
// Aici TypeScript știe că shape este Circle
return Math.PI * shape.radius ** 2;
case "square":
// Aici TypeScript știe că shape este Square
return shape.sideLength ** 2;
}
}
Operatorul in
Operatorul in verifică dacă un obiect are o anumită proprietate.
Este util pentru restrângerea reuniunilor ale căror membri au nume diferite de proprietăți.
Verificarea existenței unei proprietăți
Doar Dog are o proprietate bark, așa că "bark" in animal restrânge animal la Dog. Altfel, trebuie să fie un Cat.
in folosit ca gardă de tip.
Declară o interfață Dog cu o metodă bark(): void și o interfață Cat cu meow(): void, apoi o funcție makeSound(animal: Dog | Cat) care apelează bark() când "bark" in animal și meow() în caz contrar. Doar definiții, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
function makeSound(animal: Dog | Cat) {
if ("bark" in animal) {
// Aici TypeScript știe că animal este Dog
animal.bark();
} else {
// Aici TypeScript știe că animal este Cat
animal.meow();
}
}
Funcții de aserțiune de tip
O funcție de aserțiune de tip este o gardă de tip specială. În loc să returneze true sau false, ea aruncă o eroare atunci când verificarea eșuează. După apel, TypeScript tratează valoarea ca fiind de tipul asertat.
Sunt utile pentru validarea datelor în timpul execuției.
Funcții de aserțiune
asserts value is string restrânge value la string după apel. asserts condition îi spune lui TypeScript că, din acel punct înainte, condiția este adevărată, așa că value devine un number.
assertIsString(value: unknown): asserts value is string, care aruncă Valoarea nu este un string pentru valori care nu sunt string-uri, și assert(condition: unknown, message: string): asserts condition, care aruncă mesajul primit când condiția este falsă. Apoi scrie processInput(input: unknown), care apelează assertIsString și afișează input.toUpperCase(), și processNumber(value: unknown): number, care asertează typeof value === 'number' și returnează value * 2. Doar definiții, nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat
// Funcție de aserțiune de tip
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Valoarea nu este un string');
}
}
// Funcție de aserțiune de tip cu eroare personalizată
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
// Utilizare
function processInput(input: unknown) {
assertIsString(input);
// input are acum tipul string
console.log(input.toUpperCase());
}
// Cu eroare personalizată
function processNumber(value: unknown): number {
assert(typeof value === 'number', 'Valoarea trebuie să fie un number');
// value are acum tipul number
return value * 2;
}
Bune practici
Când folosești fiecare gardă de tip:
- Folosește
typeofpentru tipuri primitive (string, number, boolean și așa mai departe) - Folosește
instanceofpentru instanțe de clase și obiecte incluse în limbaj - Folosește gărzi de tip definite de utilizator pentru logică de validare complexă
- Folosește reuniuni discriminate pentru tipuri înrudite care au în comun o proprietate discriminant
- Folosește operatorul
inpentru a verifica dacă există o proprietate - Folosește funcții de aserțiune de tip pentru validări la execuție care trebuie să arunce o eroare
Considerații de performanță:
typeofșiinstanceofsunt foarte rapide- Evită logica grea în interiorul gărzilor de tip definite de utilizator atunci când performanța contează
- Pune verificările costisitoare care rulează de multe ori într-o funcție cu predicat de tip