Az interfész egy objektum alakja. Egy szabványos JavaScript objektum key:value párok leképezése. A JavaScript objektum kulcsai szinte minden esetben karakterláncok, értékeik pedig bármilyen támogatott JavaScript értékek (primitív vagy absztrakt).

Egy interfész megmondja a TypeScript fordítónak, hogy egy objektumnak milyen tulajdonságnevei lehetnek, és a hozzájuk tartozó értéktípusok. Ezért az interfész egy típus, és absztrakt típus, mivel primitív típusokból áll.

Amikor egy objektumot tulajdonságokkal (kulcsokkal) és értékekkel definiálunk, a TypeScript implicit interfészt hoz létre az objektumban lévő tulajdonságnevek és azok értékeinek adattípusa alapján. Ez a típuskövetkeztetés miatt történik.

(object-shape.ts)

A fenti példában létrehoztunk egy student objektumot firstName, lastName, age és getSalary mezőkkel, és hozzáadtunk néhány kezdeti értéket. Ezen információk felhasználásával a TypeScript létrehoz egy implicit interfész típust a student számára.

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

Az interfész olyan, mint egy objektum, de csak az objektum tulajdonságairól és azok típusairól tartalmaz információt. Létrehozhatunk interfész típust is, és adhatunk neki nevet, hogy használhassuk az objektumértékek megjegyzésére, de itt ennek az interfésznek nincs neve, mivel implicit módon jött létre. Összehasonlíthatja ezt az előző leckében szereplő függvénytípussal, amelyet először implicit módon hoztunk létre, majd explicit módon hoztunk létre egy függvénytípust a type alias használatával.

Próbáljunk meg szórakozni az objektum tulajdonságaival, miután definiáltuk.

(shape-override.ts)

Amint a fenti példából látható, a TypeScript megjegyzi egy objektum alakját, mivel a ross típusa az implicit interfész. Ha megpróbáljuk egy tulajdonság értékét felülírni az interfészben megadottól eltérő típusú értékkel, vagy megpróbálunk egy új, az interfészben nem megadott tulajdonságot hozzáadni, akkor a TypeScript fordító nem fordítja le a programot.

Ha azt szeretnénk, hogy egy objektumnak alapvetően bármilyen tulajdonsága legyen, akkor explicit módon jelölhetünk egy any értéket, és a TypeScript fordító nem fog következtetni a típusra a hozzárendelt objektum értékéből. Vannak más, jobb módszerek is, hogy pontosan ezt érjük el, és ezeket fogjuk végigvenni ebben a cikkben.

(shape-override-any.ts)

Interfész deklaráció

Az eddig látott implicit interfész ugyan technikailag egy típus, de nem volt explicit módon definiálva. Mint tárgyaltuk, egy interfész nem más, mint az a forma, amit egy objektum felvehet. Ha van egy függvényünk, amely elfogad egy argumentumot, amelynek objektumnak kellene lennie, de egy bizonyos alakúnak, akkor ezt az argumentumot (paramétert) egy interfész típussal kell annotálnunk.

(argument-with-shape.ts)

A fenti példában definiáltunk egy getPersonInfo függvényt, amely elfogad egy objektum argumentumot, amely firstName, lastName, age és getSalary meghatározott adattípusú mezőkkel rendelkezik. Vegyük észre, hogy a :<type> annotációval egy olyan objektumot használtunk típusként, amely tartalmazza a tulajdonságneveket és a hozzájuk tartozó típusokat. Ez egy példa az anonim interfészre, mivel az interfésznek nincs neve, inline használtuk.

Az egész egy kicsit bonyolultnak tűnik a kezeléséhez. Ha az ross objektum bonyolultabbá válik, és több helyen kell használni, a TypeScript csak egy olyan dolognak tűnik, amit kezdetben szerettél, de most már csak egy nehéz dolog kezelni. A probléma megoldására egy interfész típust definiálunk a interface kulcsszóval.

(argument-with-interface.ts)

A fenti példában egy objektum alakját leíró Person interfészt definiáltunk, de ezúttal van egy név, amivel hivatkozhatunk erre a típusra. Ezt a típust használtuk a ross változó, valamint a getPersonIfo függvény person argumentumának megjegyzésére. Ez tájékoztatja a TypeScriptet, hogy ezeket az entitásokat a Person alakkal szemben érvényesítse.

Miért használjunk interfészt?

Az interfész típus fontos lehet egy adott alak érvényesítéséhez. Jellemzően a JavaScriptben futásidőben vakon bízunk abban, hogy egy objektum mindig tartalmazni fog egy adott tulajdonságot, és ennek a tulajdonságnak mindig egy adott típusú értéke lesz, mint például {age: 21, ...}.

Amikor ténylegesen elkezdünk műveleteket végezni ezen a tulajdonságon anélkül, hogy először ellenőriznénk, hogy létezik-e az adott tulajdonság az objektumon, vagy hogy az értéke az-e, amit vártunk, a dolgok rosszul sülhetnek el, és ez utána használhatatlanná teheti az alkalmazást. Például {age: '21', ...}, itt age értéke egy string.

Az interfészek biztonságos mechanizmust biztosítanak az ilyen forgatókönyvek kezelésére fordítási időben. Ha véletlenül olyan tulajdonságot használunk egy nem létező objektumon, vagy egy tulajdonság értékét használjuk illegális műveletben, a TypeScript fordító nem fordítja le a programunkat. Lássunk egy példát.

(interface-safety.ts)

A fenti példában a printStudent függvényen belül a _student argumentum name tulajdonságát próbáljuk használni. Mivel az _student argumentum a Student interfész típusa, a TypeScript fordító hibát dob a fordítás során, mivel ez a tulajdonság nem létezik a Student interfészben.

Hasonlóképpen, a 100 — _student.firstName nem érvényes művelet, mivel a firstName tulajdonság a string típusa, és amikor legutóbb ellenőriztem, nem lehet kivonni egy string-et egy number-ból a JavaScriptben (az eredmény NaN).

A fenti példában a getSalary mezőhöz a hagyományos módon írtunk függvénytípust. Használhatjuk azonban ugyanerre a test nélküli függvényszintaxist is, amit általában az interfészekben használunk.

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

Optionális tulajdonságok

Néha szükség van arra, hogy egy objektum rendelkezzen egy olyan tulajdonsággal, amely egy adott adattípus adatait tartalmazza, de nem kötelező, hogy ez a tulajdonság az objektumon legyen. Ez hasonló az előző leckében megismert opcionális függvényparaméterekhez.

Az ilyen tulajdonságokat opcionális tulajdonságoknak nevezzük. Egy interfész tartalmazhat opcionális tulajdonságokat, és az opcionális függvényparaméterekhez hasonlóan a ?:Type annotációt használjuk ezek ábrázolására.

(optional-properties.ts)

A fenti példában a Student interfész rendelkezik a age tulajdonsággal, amely opcionális. Ha azonban a age tulajdonságot megadjuk, akkor annak number típusú értékkel kell rendelkeznie.

A ross objektum esetében, amely a Student interfész típusa, nem adtuk meg a age tulajdonság értékét, ami legális, azonban a monica esetében megadtuk a age tulajdonságot, de annak értéke string, ami nem legális. Ezért a TypeScript fordító hibát dob.

A hiba furcsának tűnhet, de valójában van értelme. Ha a age tulajdonság nem létezik egy objektumon, akkor a object.age undefined értéket fog visszaadni, ami egy undefined típusú. Ha létezik, akkor az értéknek number típusúnak kell lennie.

Az age tulajdonság értéke tehát vagy undefined vagy number típusú lehet, amit a TypeScriptben az union szintaxis number | undefined segítségével ábrázolunk.

💡 A típusuniót egy Type System leckében fogjuk megtanulni.

Az opcionális tulajdonságok azonban komoly problémákat okoznak a program végrehajtása során. Képzeljük el, ha egy aritmetikai műveletben a age tulajdonságot használjuk, de annak értéke undefined. Ez egyfajta komoly probléma.

De a jó dolog az, hogy a TypeScript fordító nem engedi meg illegális műveletek végrehajtását egy opcionális tulajdonságon, mivel annak értéke undefined lehet.

(optional-properties-safety.ts)

A fenti példában a age tulajdonságon végzünk aritmetikai műveletet, ami illegális, mert ennek a tulajdonságnak az értéke a futási időben number vagy undefined lehet. A undefined-n végzett aritmetikai műveletek eredménye NaN (nem szám).

💡 A fenti programhoz azonban a --strictNullChecks flag-et false-re kellett állítanunk, ami egy TypeScript fordítói flag. Ha megadjuk ezt az opciót, akkor a fenti program tökéletesen lefordítható.

Az ilyen hiba vagy figyelmeztetés elkerülése érdekében kifejezetten meg kell mondanunk a TypeScript fordítónak, hogy ez a tulajdonság number típusú, és nem a number vagy undefined. Ehhez használjuk a type assertiont (AKA type conversion vagy typecasting).

(optional-properties-safety-override.ts)

A fenti programban a (_student.age as number)-t használtuk, amely a _student.age típusát number | undefined-ről number-ra konvertálja. Ez egy módja annak, hogy megmondjuk a TypeScript fordítónak, hogy “Hé, ez egy szám”. De jobb megoldás lenne, ha futásidőben is ellenőriznénk, hogy _student.age undefined-e, és ezután végeznénk el az aritmetikai műveletet.

💡 A típus-nyilatkozatokról egy Type System leckében fogunk tanulni.

Funkció típusa interfész használatával

Nem csak egy egyszerű objektum alakját, hanem egy interfész egy függvény szignatúráját is leírhatja. Az előző leckében a függvénytípus leírására a type alias-t használtuk, de az interfészek is képesek erre.

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

A szintaxis, amellyel egy interfészt függvénytípusként deklarálhatunk, hasonló magához a függvényaláíráshoz. Amint a fenti példából láthatjuk, az interfész teste pontosan egy névtelen függvény szignatúráját tartalmazza, természetesen a test nélkül. Itt a paraméternevek nem számítanak.

(function-signature.ts)

A fenti példában IsSumOdd interfészt definiáltunk, amely egy olyan függvénytípust határoz meg, amely két number típusú argumentumot fogad el és egy boolean értéket ad vissza. Most már használhatjuk ezt a típust egy függvény leírására, mivel a IsSumOdd interfész típus egyenértékű a (x: number, y: number) => boolean függvénytípussal.

A névtelen metódusaláírással rendelkező interfész leír egy függvényt. De egy függvény a JavaScript birodalmában egy objektum is, ami azt jelenti, hogy ugyanúgy adhatunk tulajdonságokat egy függvény értékéhez, mint egy objektumhoz. Ezért teljesen legális, hogy bármilyen tulajdonságot definiálhatunk egy függvénytípusú interfészen.

(function-signature-with-methods.ts)

A fenti példában a függvényt leíró IsSumOdd interfészhez type és calculate tulajdonságokat adtunk. A Object.assign módszerrel összevonjuk a type és calculate tulajdonságokat egy függvény értékével.

A függvény típusú interfészek hasznosak lehetnek a konstruktorfüggvények leírásához. A konstruktorfunkció hasonlít egy osztályhoz, amelynek feladata az objektumok (példányok) létrehozása. Az ES5-ig csak konstruktorfunkcióink voltak a class JavaScriptben egy class utánzására. Ezért a TypeScript az osztályokat konstruktorfüggvényekké fordítja, ha a ES5 vagy az alatti osztályokat célozza meg.

💡 Ha többet szeretne megtudni a konstruktorfüggvényről, kövesse ezt a cikket.

Ha az anonim függvény aláírás elé new kulcsszót teszünk az interfészben, az konstruálhatóvá teszi a függvényt. Ez azt jelenti, hogy a függvényt csak az new kulcsszó használatával lehet meghívni objektumok létrehozására, nem pedig hagyományos függvényhívással. Egy minta konstruktorfüggvény így néz ki:

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

Szerencsére nem kell konstruktorfüggvényekkel dolgoznunk, mivel a TypeScript biztosítja a class kulcsszót egy osztály létrehozására, amivel sokkal egyszerűbb dolgozni, mint egy konstruktorfüggvénnyel, higgye el nekem. Valójában egy class mélyen a JavaScriptben egy konstruktorfunkció. Próbálja ki az alábbi példát.

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

Az osztály és a konstruktorfunkció egy és ugyanaz a dolog. Az egyetlen különbség az, hogy a class gazdag OOP-szintaxist biztosít számunkra. Ezért egy konstruktorfunkció típusú interfész egy osztályt képvisel.

(function-signature-with-new.ts)

A fenti példában a Animal osztályt egy konstruktorfunkcióval definiáltuk, amely egy string típusú argumentumot fogad el. Ezt tekinthetjük konstruktorfüggvénynek, amely a Animal konstruktorhoz hasonló aláírással rendelkezik.

A AnimalInterface konstruktorfüggvényt definiál, mivel a new kulcsszóval ellátott anonymous function előtaggal rendelkezik. Ez azt jelenti, hogy a Animal osztály a AnimalInterface típusának minősül. Itt a AnimalInterface interfész típusa egyenértékű a new (sound: string) => any függvénytípussal.

A createAnimal függvény ctor AnimalInterface típusú argumentumot fogad el, tehát átadhatjuk a Animal osztályt argumentumértékként. A Animal osztály getSound metódusaláírását nem fogjuk tudni hozzáadni a AnimalInterface osztályhoz, ennek okát az Osztályok leckében magyarázzuk el.

Indexálható típusok

Az indexálható objektum olyan objektum, amelynek tulajdonságaihoz indexaláírással lehet hozzáférni, mint a obj. Ez az alapértelmezett módja egy tömbelem elérésének, de mi is megtehetjük ezt az objektum esetében.

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

Az objektumunknak időnként tetszőleges számú tulajdonsága lehet, anélkül, hogy határozott alakja lenne. Ebben az esetben egyszerűen használhatjuk a object típust. Ez az object típus azonban minden olyan értéket definiál, amely nem number, string, boolean, symbol, null vagy undefined, ahogy azt az alaptípusok leckében tárgyaltuk.

Ha szigorúan ellenőriznünk kell, hogy egy érték egyszerű JavaScript objektum-e, akkor gondban lehetünk. Ezt egy olyan interfésztípus segítségével oldhatjuk meg, amely a tulajdonság nevének index aláírásával rendelkezik.

interface SimpleObject {
: any;
}

A SimpleObject interfész egy string kulcsokkal rendelkező objektum alakját határozza meg, amelynek értékei any adattípusúak lehetnek. Itt a key tulajdonságnév csak helyőrzőnek szolgál, mivel szögletes zárójelbe van zárva.

(indexable-type-object.ts)

A fenti példában ross és monica típusú SimpleObject felületű objektumot definiáltunk. Mivel ezek az objektumok string kulcsokat és any adattípusú értékeket tartalmaznak, ez teljesen legális.

Ha a monica-ban a number típusú 1 kulcs miatt zavarban vagyunk, ez is legális, mivel a JavaScriptben az objektumok vagy tömbelemek number vagy string kulcsokkal indexelhetők, ahogy az alább látható.

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

Ha pontosabban kell meghatározni a kulcsok és értékeik típusát, azt is biztosan meg tudjuk tenni. Például definiálhatunk egy indexelhető interfésztípust number típusú kulcsokkal és number típusú értékekkel, ha akarjuk.

💡 Egy indexaláírás kulcstípusának vagy string vagy number típusúnak kell lennie.

(indexable-type-number.ts)

A fenti példában egy LapTimes interfészt definiáltunk, amely number típusú tulajdonságneveket és number típusú értékeket tartalmazhat. Ez az interfész olyan adatstruktúrát reprezentálhat, amely number kulcsokkal indexelhető, ezért a ross tömb és a monica és joey objektumok legálisak.

A rachel objektum azonban nem felel meg a LapTimes alakjának, mivel a one kulcs string, és csak string, például rachel segítségével érhető el, semmi mással nem. Ezért a TypeScript fordító a fentiek szerint hibát fog dobni.

Egy indexelhető interfésztípusban lehetséges, hogy egyes tulajdonságok kötelezőek, mások pedig opcionálisak legyenek. Ez nagyon hasznos lehet, amikor egy objektumnak egy bizonyos alakra van szüksége, de igazából nem számít, ha extra és nem kívánt tulajdonságokat kapunk az objektumban.

(indexable-type-required-properties.ts)

A fenti példában egy LapTimes interfészt definiáltunk, amelynek tartalmaznia kell a name tulajdonságot string értékkel és a age opcionális tulajdonságot number értékkel. Egy LapTimes típusú objektumnak tetszőleges tulajdonságai is lehetnek, amelyeknek a kulcsa number és az értéke is number kell, hogy legyen.

A ross objektum akkor is érvényes LapTimes objektum, ha nem rendelkezik a age tulajdonsággal, mivel az opcionális. A monica objektum azonban rendelkezik a age tulajdonsággal, de értéke string, ezért nem felel meg a LapTimes interfésznek.

A joey objektum szintén nem felel meg a LapTimes interfésznek, mivel rendelkezik egy gender tulajdonsággal, amely egy string típusú. A rachel objektumnak nincs name tulajdonsága, ami a LapTimes interfészben szükséges.

💡 Van néhány gubanc, amire figyelnünk kell az indexelhető típusok használata során. Ezeket ebben a dokumentációban említjük.

Extending Interface

Az osztályokhoz hasonlóan egy interfész is örökölhet tulajdonságokat más interfészekből. Azonban a JavaScriptben az osztályokkal ellentétben egy interfész több interfészből is örökölhet. Egy interfész örökléséhez a extends kulcsszót használjuk.

(extend-interface.ts)

Az interfész kiterjesztésével a gyermek interfész megkapja a szülő interfész összes tulajdonságát. Ez nagyon hasznos, ha több interfésznek közös a szerkezete, és el akarjuk kerülni a kódduplikációt azáltal, hogy a közös tulajdonságokat kivesszük egy közös interfészbe, amely később örökölhető.

(extend-multiple-interfaces.ts)

A fenti példában létrehoztunk egy Student interfészt, amely a Person és Player interfész tulajdonságait örökli.

Multiple Interface Declarations

Az előző részben megtanultuk, hogyan örökölheti egy interfész egy másik interfész tulajdonságait. Ezt a extend kulcsszó segítségével tettük meg. Ha azonban azonos nevű interfészeket ugyanabban a modulban (fájlban) deklarálunk, a TypeScript összevonja a tulajdonságaikat, mindaddig, amíg a tulajdonságneveik különböznek, vagy az ütköző tulajdonságtípusaik megegyeznek.

(merged-interface.ts)

A fenti példában a Person interfészt többször deklaráltuk. Ez egyetlen Person interfészdeklarációt eredményez az összes Person interfészdeklaráció tulajdonságainak összevonásával.

💡 Az Osztályok leckében megtanultuk, hogy egy class implicit módon deklarál egy interfészt, és egy interfész kiterjesztheti ezt az interfészt. Tehát ha egy programnak van egy Person osztálya és egy Person interfésze, akkor a végső Person típus (interfész) az osztály és az interfész között összevont tulajdonságokkal rendelkezik.

Hálózati interfészek

Egy interfésznek lehetnek mélyen egymásba ágyazott struktúrái. Az alábbi példában az Student interfész info mezője egy firstName és lastName tulajdonságú objektum alakját határozza meg.

(nested-shape.ts)

Hasonlóképpen teljesen legális, hogy egy interfész mezőjének egy másik interfész típusa legyen. A következő példában a Student interfész info mezőjének a típusa Person interfész.

(nested-interface.ts)

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.