O interfață este o formă a unui obiect. Un obiect JavaScript standard este o hartă de perechi key:value. Cheile obiectelor JavaScript sunt, în aproape toate cazurile, șiruri de caractere, iar valorile lor sunt orice valori JavaScript acceptate (primitive sau abstracte).

O interfață îi spune compilatorului TypeScript despre numele proprietăților pe care le poate avea un obiect și tipurile de valori corespunzătoare acestora. Prin urmare, interfața este un tip și este un tip abstract, deoarece este compusă din tipuri primitive.

Când definim un obiect cu proprietăți (chei) și valori, TypeScript creează o interfață implicită uitându-se la numele proprietăților și la tipul de date al valorilor lor din obiect. Acest lucru se întâmplă din cauza inferenței de tip.

(object-shape.ts)

În exemplul de mai sus, am creat un obiect student cu câmpurile firstName, lastName, age și getSalary și i-am atribuit niște valori inițiale. Folosind aceste informații, TypeScript creează un tip de interfață implicită pentru student.

{
firstName: string;
lastName: string;
age: number;
getSalary: (base: number) => number;
}

O interfață este la fel ca un obiect, dar conține doar informații despre proprietățile obiectului și tipurile acestora. Putem, de asemenea, să creăm un tip de interfață și să îi dăm un nume, astfel încât să o putem folosi pentru a adnota valorile obiectului, dar aici, această interfață nu are un nume, deoarece a fost creată implicit. Puteți compara acest lucru cu tipul de funcție din lecția anterioară, care a fost creat implicit la început și apoi am creat un tip de funcție în mod explicit folosind aliasul de tip.

Să încercăm să ne jucăm cu proprietățile obiectului după ce a fost definit.

(shape-override.ts)

După cum puteți vedea din exemplul de mai sus, TypeScript își amintește forma unui obiect deoarece tipul de ross este interfața implicită. Dacă încercăm să suprascriem valoarea unei proprietăți cu o valoare de alt tip decât cel specificat în interfață sau încercăm să adăugăm o nouă proprietate care nu este specificată în interfață, compilatorul TypeScript nu va compila programul.

Dacă doriți ca un obiect să aibă practic orice proprietate, atunci puteți marca explicit o valoare any și compilatorul TypeScript nu va deduce tipul din valoarea atribuită obiectului. Există și alte modalități mai bune de a realiza exact acest lucru și le vom trece în revistă în acest articol.

(shape-override-any.ts)

Declarație de interfață

Deși interfața implicită pe care am văzut-o până acum este, din punct de vedere tehnic, un tip, dar nu a fost definită explicit. După cum s-a discutat, o interfață nu este altceva decât forma pe care o poate lua un obiect. Dacă avem o funcție care acceptă un argument care ar trebui să fie un obiect, dar de o anumită formă, atunci trebuie să notăm acel argument (parametru) cu un tip de interfață.

(argument-cu-formă.ts)

În exemplul de mai sus, am definit o funcție getPersonInfo care acceptă un argument obiect care are câmpurile firstName, lastName, age și getSalary de tipuri de date specificate. Observați că am folosit un obiect care conține nume de proprietăți și tipurile corespunzătoare acestora ca tip folosind adnotarea :<type>. Acesta este un exemplu de interfață anonimă, deoarece interfața nu are un nume, a fost folosită inline.

Toate acestea par puțin complicate de gestionat. Dacă obiectul ross devine mai complicat și trebuie să fie folosit în mai multe locuri, TypeScript pare doar un lucru care v-a plăcut inițial, dar care acum este doar un lucru greu de gestionat. Pentru a rezolva această problemă, definim un tip de interfață folosind cuvântul cheie interface.

(argument-with-interface.ts)

În exemplul de mai sus, am definit o interfață Person care descrie forma unui obiect, dar de data aceasta, avem un nume pe care îl putem folosi pentru a ne referi la acest tip. Am folosit acest tip pentru a adnota variabila ross, precum și argumentul person al funcției getPersonIfo. Acest lucru va informa TypeScript să valideze aceste entități în funcție de forma Person.

De ce să folosim o interfață?

Tipul de interfață poate fi important pentru a impune o anumită formă. De obicei, în JavaScript, ne punem o încredere oarbă în timpul execuției că un obiect va conține întotdeauna o anumită proprietate și că acea proprietate va avea întotdeauna o valoare de un anumit tip, cum ar fi {age: 21, ...} ca exemplu.

Când începem efectiv să efectuăm operații asupra acelei proprietăți fără a verifica mai întâi dacă acea proprietate există pe obiect sau dacă valoarea ei este cea pe care o așteptam, lucrurile pot merge prost și vă poate lăsa aplicația inutilizabilă după aceea. De exemplu, {age: '21', ...}, aici age valoarea age este un string.

Interfețele oferă un mecanism sigur pentru a face față unor astfel de scenarii în momentul compilării. Dacă utilizați din greșeală o proprietate pe un obiect care nu există sau folosiți valoarea unei proprietăți într-o operațiune ilegală, compilatorul TypeScript nu vă va compila programul. Să vedem un exemplu.

(interface-safety.ts)

În exemplul de mai sus, încercăm să folosim proprietatea name a argumentului _student în interiorul funcției printStudent. Deoarece argumentul _student este un tip al interfeței Student, compilatorul TypeScript aruncă o eroare în timpul compilării, deoarece această proprietate nu există în interfața Student.

În mod similar, 100 — _student.firstName nu este o operație validă, deoarece proprietatea firstName este un tip de string și ultima dată când am verificat, nu poți scădea un string dintr-un number este JavaScript (rezultă NaN).

În exemplul de mai sus, am folosit modul tradițional de scriere a tipului de funcție pentru câmpul getSalary. Cu toate acestea, puteți utiliza, de asemenea, sintaxa de funcție fără corp pentru același lucru, care este utilizată în general în interfețe.

interface Student {
firstName: string;
lastName: string;
age: number;
getSalary(base: number): number;
};

Proprietăți opționale

Câteodată, aveți nevoie ca un obiect să aibă o proprietate care să dețină date de un anumit tip de date, dar nu este obligatoriu să aveți acea proprietate pe obiect. Acest lucru este similar cu parametrii opționali ai funcțiilor pe care i-am învățat în lecția anterioară.

Aceste proprietăți se numesc proprietăți opționale. O interfață poate conține proprietăți opționale și folosim adnotarea ?:Type pentru a le reprezenta, la fel ca parametrii opționali ai funcțiilor.

(optional-properties.ts)

În exemplul de mai sus, interfața Student are proprietatea age care este opțională. Cu toate acestea, dacă proprietatea age este furnizată, aceasta trebuie să aibă o valoare de tipul number.

În cazul obiectului ross, care este un tip de interfață Student, nu am furnizat valoarea proprietății age, care este legală, însă, în cazul lui monica, am furnizat proprietatea age, dar valoarea acesteia este string, care nu este legală. Prin urmare, compilatorul TypeScript aruncă o eroare.

Eroarea poate părea ciudată, dar de fapt are sens. Dacă proprietatea age nu există pe un obiect, object.age va returna undefined care este un tip de undefined. Dacă există, atunci valoarea trebuie să fie de tipul number.

Înseamnă că valoarea proprietății age poate fi fie de tipul undefined, fie de tipul number, care în TypeScript este reprezentată folosind sintaxa de uniune number | undefined.

💡 Vom învăța uniunile de tip într-o lecție despre Sistemul de tipuri.

Cu toate acestea, proprietățile opționale pun probleme serioase în timpul execuției programului. Să ne imaginăm că folosim proprietatea age într-o operație aritmetică, dar valoarea ei este undefined. Aceasta este un fel de problemă serioasă.

Dar un lucru bun este că compilatorul TypeScript nu permite efectuarea de operații ilegale pe o proprietate opțională, deoarece valoarea acesteia poate fi undefined.

(optional-properties-safety.ts)

În exemplul de mai sus, efectuăm o operație aritmetică pe proprietatea age, care este ilegală deoarece valoarea acestei proprietăți poate fi number sau undefined în timpul de execuție. Efectuarea de operații aritmetice pe undefined are ca rezultat NaN (nu este un număr).

💡 Cu toate acestea, pentru programul de mai sus, a trebuit să setăm indicatorul --strictNullChecks la false, care este un indicator al compilatorului TypeScript. Dacă furnizăm această opțiune, programul de mai sus se compilează foarte bine.

Pentru a evita această eroare sau avertisment, trebuie să spunem în mod explicit compilatorului TypeScript că această proprietate este un tip de number și nu number sau undefined. Pentru aceasta, folosim aserțiunea de tip (AKA type conversion sau typecasting).

(optional-properties-safety-override.ts)

În programul de mai sus, am folosit (_student.age as number) care convertește tipul lui _student.age de la number | undefined la number. Acesta este un mod de a spune compilatorului TypeScript: „Hei, acesta este un număr”. Dar o modalitate mai bună de a gestiona acest lucru ar fi să se verifice, de asemenea, dacă _student.age este undefined în timpul execuției și apoi să se efectueze operația aritmetică.

💡 Vom învăța despre aserțiunile de tip într-o lecție despre sistemul de tipuri.

Tipul funcției folosind o interfață

Nu numai forma unui obiect simplu, dar o interfață poate descrie, de asemenea, semnătura unei funcții. În lecția anterioară, am folosit aliasul de tip pentru a descrie un tip de funcție, dar și interfețele pot face acest lucru.

interface InterfaceName {
(param: Type): Type;
}

Sintaxa pentru a declara o interfață ca tip de funcție este similară cu semnătura funcției în sine. După cum puteți vedea din exemplul de mai sus, corpul interfeței conține exact semnătura unei funcții anonime, fără corp, bineînțeles. Aici numele parametrilor nu contează.

(function-signature.ts)

În exemplul de mai sus, am definit IsSumOddinterfața care definește un tip de funcție care acceptă două argumente de tip number și returnează o valoare boolean. Acum puteți utiliza acest tip pentru a descrie o funcție deoarece tipul de interfață IsSumOdd este echivalent cu tipul de funcție (x: number, y: number) => boolean.

O interfață cu o semnătură de metodă anonimă descrie o funcție. Dar o funcție în domeniul JavaScript este, de asemenea, un obiect, ceea ce înseamnă că puteți adăuga proprietăți la valoarea unei funcții la fel ca un obiect. Prin urmare, este perfect legal să puteți defini orice proprietăți pe o interfață de tip funcție.

(function-signature-with-methods.ts)

În exemplul de mai sus, am adăugat proprietățile type și calculate pe interfața IsSumOdd care descrie o funcție. Folosind metoda Object.assign, fuzionăm proprietățile type și calculate cu o valoare a funcției.

Interfețele de tip funcție pot fi utile pentru a descrie funcțiile de tip constructor. O funcție constructor este similară cu o clasă a cărei sarcină este de a crea obiecte (instanțe). Am avut funcții de constructor doar până la ES5 pentru a imita o class în JavaScript. Prin urmare, TypeScript compilează clasele în funcții de constructor dacă vizați ES5 sau mai jos.

💡 Dacă doriți să aflați mai multe despre funcția de constructor, urmăriți acest articol.

Dacă punem cuvântul cheie new înaintea semnăturii funcției anonime din interfață, aceasta face funcția construibilă. Asta înseamnă că funcția poate fi invocată doar folosind cuvântul cheie new pentru a genera obiecte și nu folosind un apel de funcție obișnuit. Un exemplu de funcție constructor arată ca mai jos.

function Animal( _name ) {
this.name = _name;
}var dog = new Animal( 'Tommy' );
console.log( dog.name ); // Tommy

Din fericire, nu trebuie să lucrăm cu funcții constructor deoarece TypeScript oferă cuvântul cheie class pentru a crea o clasă care este mult mai ușor de lucrat decât o funcție constructor, credeți-mă. De fapt, un class în profunzime este o funcție de constructor în JavaScript. Încercați exemplul de mai jos.

class Animal{
constructor( _name ) {
this.name = _name;
}
}console.log( typeof Animal ); // "function"

O clasă și o funcție constructor sunt unul și același lucru. Singura diferență este că class ne oferă o sintaxă OOP bogată cu care să lucrăm. Prin urmare, o interfață de tip funcție constructor reprezintă o clasă.

(function-signature-with-new.ts)

În exemplul de mai sus, am definit clasa Animal cu o funcție constructor care acceptă un argument de tip string. Puteți considera că aceasta este o funcție constructor care are o semnătură similară constructorului Animal.

Clasa AnimalInterface definește o funcție constructor deoarece are funcția anonimă prefixată cu cuvântul cheie new. Aceasta înseamnă că clasa Animal se califică pentru a fi un tip al AnimalInterface. Aici, tipul de interfață AnimalInterface este echivalent cu tipul de funcție new (sound: string) => any.

Funcția createAnimal acceptă ctor argument de tip AnimalInterface, prin urmare putem trece clasa Animal ca valoare de argument. Nu vom putea adăuga semnătura metodei getSound a clasei Animal în AnimalInterface, iar motivul este explicat în lecția despre clase.

Tipuri indexabile

Un obiect indexabil este un obiect ale cărui proprietăți pot fi accesate folosind o semnătură de indexare ca obj. Aceasta este modalitatea implicită de accesare a unui element de tablou, dar putem face acest lucru și pentru obiect.

var a = ;
var o = { one: 1, two: 2, three: 3 };console.log( a ); // 1
console.log( a ); // 1 (same as `a.one`)

Atunci, obiectul dumneavoastră poate avea un număr arbitrar de proprietăți fără o formă definită. În acest caz, puteți folosi pur și simplu tipul object. Cu toate acestea, acest tip object definește orice valoare care nu este number, string, boolean, symbol, null sau undefined, așa cum s-a discutat în lecția despre tipurile de bază.

Dacă avem nevoie să verificăm strict dacă o valoare este un obiect JavaScript simplu, atunci am putea avea o problemă. Acest lucru poate fi rezolvat folosind un tip de interfață cu o semnătură de index pentru numele proprietății.

interface SimpleObject {
: any;
}

Interfața SimpleObject definește forma unui obiect cu string chei ale cărui valori pot fi de tipul de date any. Aici, numele proprietății key este folosit doar ca spațiu rezervat, deoarece este cuprins între paranteze pătrate.

(indexable-type-object.ts)

În exemplul de mai sus, am definit obiectele ross și monica de tip interfață SimpleObject. Deoarece aceste obiecte conțin chei string și valori de tipul de date any, este perfect legal.

Dacă sunteți confuzi cu privire la cheia 1 din monica, care este un tip de number, acest lucru este legal, deoarece obiectele sau elementele de tip array din JavaScript pot fi indexate folosind chei number sau string, așa cum se arată mai jos.

var o = { 0: 'Zero', '1': 'One' };
var a = ;console.log( o ); // Zero
console.log( o ); // One
console.log( a ); // One
console.log( a ); // One

Dacă trebuie să fim mai preciși cu privire la tipul de chei și valorile lor, cu siguranță putem face și acest lucru. De exemplu, dacă dorim, putem defini un tip de interfață indexabilă cu chei de tip number și valori de tip number.

💡 Tipul cheii unei semnături de indexare trebuie să fie fie string sau number.

(indexable-type-number.ts)

În exemplul de mai sus, am definit o interfață LapTimes care poate conține nume de proprietăți de tip number și valori de tip number. Această interfață poate reprezenta o structură de date care poate fi indexată cu ajutorul cheilor number, prin urmare array-ul ross și obiectele monica și joey sunt legale.

Cu toate acestea, obiectul rachel nu respectă forma lui LapTimes deoarece cheia one este un string și poate fi accesat numai cu ajutorul string precum rachel și nimic altceva. Prin urmare, compilatorul TypeScript va arunca o eroare, așa cum se arată mai sus.

Este posibil ca unele proprietăți să fie obligatorii și altele opționale într-un tip de interfață indexabilă. Acest lucru poate fi destul de util atunci când avem nevoie ca un obiect să aibă o anumită formă, dar chiar nu contează dacă obținem proprietăți suplimentare și nedorite în obiect.

(indexable-type-required-properties.ts)

În exemplul de mai sus, am definit o interfață LapTimes care trebuie să conțină proprietatea name cu valoarea string și proprietatea opțională age cu valoarea number. Un obiect de tip LapTimes poate avea, de asemenea, proprietăți arbitrare ale căror chei trebuie să fie number și ale căror valori trebuie să fie, de asemenea, number.

Obiectul ross este un obiect LapTimes valid chiar dacă nu are proprietatea age deoarece aceasta este opțională. Cu toate acestea, monica are proprietatea age, dar valoarea sa este string, prin urmare nu respectă interfața LapTimes.

Obiectul joey, de asemenea, nu respectă interfața LapTimes deoarece are o proprietate gender care este un tip de string. Obiectul rachel nu are proprietatea name care este necesară în interfața LapTimes.

💡 Există câteva probleme la care trebuie să fim atenți atunci când folosim tipuri indexabile. Acestea sunt menționate în această documentație.

Extinderea interfeței

Ca și clasele, o interfață poate moșteni proprietăți de la alte interfețe. Cu toate acestea, spre deosebire de clasele din JavaScript, o interfață poate moșteni din mai multe interfețe. Folosim cuvântul cheie extends pentru a moșteni o interfață.

(extend-interface.ts)

Prin extinderea unei interfețe, interfața copil primește toate proprietățile interfeței părinte. Acest lucru este destul de util atunci când mai multe interfețe au o structură comună și dorim să evităm duplicarea codului prin preluarea proprietăților comune într-o interfață comună care poate fi moștenită ulterior.

(extend-multiple-interfaces.ts)

În exemplul de mai sus, am creat o interfață Student care moștenește proprietăți de la interfața Person și Player.

Declarații de interfețe multiple

În secțiunea anterioară, am învățat cum o interfață poate moșteni proprietățile unei alte interfețe. Acest lucru a fost realizat cu ajutorul cuvântului cheie extend. Cu toate acestea, atunci când interfețe cu același nume sunt declarate în cadrul aceluiași modul (fișier), TypeScript fuzionează proprietățile lor împreună atâta timp cât au nume de proprietăți distincte sau dacă tipurile de proprietăți aflate în conflict sunt aceleași.

(merged-interface.ts)

În exemplul de mai sus, am declarat interfața Person de mai multe ori. Acest lucru va avea ca rezultat o singură declarație de interfață Person prin fuzionarea proprietăților tuturor declarațiilor de interfață Person.

💡 În lecția despre clase, am învățat că un class declară implicit o interfață și că o interfață poate extinde acea interfață. Astfel, dacă un program are o clasă Person și o interfață Person, atunci tipul final Person (interfața) va avea proprietăți fuzionate între clasă și interfață.

Interfețe înglobate

Orice interfață poate avea structuri adânc înglobate. În exemplul de mai jos, câmpul info al interfeței Student definește forma unui obiect cu proprietățile firstName și lastName.

(nested-shape.ts)

În mod similar, este perfect legal ca un câmp al unei interfețe să aibă tipul unei alte interfețe. În exemplul următor, câmpul info al interfeței Student are tipul de interfață Person.

(nested-interface.ts)

Lasă un răspuns

Adresa ta de email nu va fi publicată.