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.
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.
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.
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.
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.
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.
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.
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.
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-etfalse
-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).
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.
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.