Rajapinta on objektin muoto. Tavallinen JavaScript-objekti on key:value
-parien kartta. JavaScript-objektin avaimet ovat lähes kaikissa tapauksissa merkkijonoja ja niiden arvot ovat mitä tahansa tuettuja JavaScript-arvoja (primitiivisiä tai abstrakteja).
Liittymä kertoo TypeScript-kääntäjälle ominaisuuksien nimet, joita objektilla voi olla, ja niitä vastaavat arvotyypit. Rajapinta on siis tyyppi ja se on abstrakti tyyppi, koska se koostuu primitiivisistä tyypeistä.
Kun määrittelemme objektin, jolla on ominaisuuksia (avaimia) ja arvoja, TypeScript luo implisiittisen rajapinnan tarkastelemalla objektin ominaisuuksien nimiä ja niiden arvojen tietotyyppiä. Tämä tapahtuu tyypin päättelyn vuoksi.
Yllä olevassa esimerkissä olemme luoneet objektin student
, jossa on firstName
, lastName
, age
ja getSalary
kentät, ja määrittäneet sille joitain alkuarvoja. Näiden tietojen avulla TypeScript luo student
:lle implisiittisen rajapintatyypin.
{
firstName: string;
lastName: string;
age: number;
getSalary: (base: number) => number;
}
Rajapinta on aivan kuten objekti, mutta se sisältää vain tiedot objektin ominaisuuksista ja niiden tyypeistä. Voimme myös luoda rajapintatyypin ja antaa sille nimen, jotta voimme käyttää sitä objektin arvojen merkitsemiseen, mutta tässä tapauksessa tällä rajapinnalla ei ole nimeä, koska se on luotu implisiittisesti. Voit verrata tätä edellisen oppitunnin funktiotyyppiin, joka luotiin ensin implisiittisesti ja sitten loimme funktiotyypin eksplisiittisesti käyttämällä type alias -tyyppiä.
Kokeillaan sotkea objektin ominaisuuksia sen määrittelyn jälkeen.
Kuten yllä olevasta esimerkistä näkyy, TypeScript muistaa objektin muodon, koska tyyppi ross
on implisiittinen rajapinta. Jos yritämme ohittaa ominaisuuden arvon muulla kuin rajapinnassa määritellyn tyyppisellä arvolla tai yritämme lisätä uuden ominaisuuden, jota ei ole määritetty rajapinnassa, TypeScript-kääntäjä ei käännä ohjelmaa.
Jos haluat, että objektilla on periaatteessa mikä tahansa ominaisuus, voit merkitä nimenomaisesti arvon any
eikä TypeScript-kääntäjä päättele tyyppiä objektin osoitetusta arvosta. On muitakin parempia tapoja saavuttaa juuri tämä, ja käymme ne läpi tässä artikkelissa.
Liitännäisrajapinnan julistus
Vaikka tähän asti näkemämme implisiittinen rajapinta on teknisesti ottaen tyyppiä, mutta sitä ei määritelty eksplisiittisesti. Kuten käsiteltiin, rajapinta ei ole mitään muuta kuin muoto, jonka objekti voi ottaa. Jos meillä on funktio, joka hyväksyy argumentin, jonka pitäisi olla objekti, mutta jolla on tietty muoto, meidän täytyy merkitä tuo argumentti (parametri) rajapintatyypillä.
Yllä olevassa esimerkissä olemme määritelleet funktion getPersonInfo
, joka hyväksyy objektiargumentin, jolla on firstName
, lastName
, age
ja getSalary
kentät, joilla on määritetyt tietotyypit. Huomaa, että olemme käyttäneet tyyppinä objektia, joka sisältää ominaisuuksien nimet ja niitä vastaavat tyypit käyttäen :<type>
-merkintää. Tämä on esimerkki anonyymista rajapinnasta, koska rajapinnalla ei ole nimeä, vaan sitä käytettiin inline.
Tämä kaikki vaikuttaa hieman monimutkaiselta käsitellä. Jos ross
objekti muuttuu monimutkaisemmaksi ja sitä täytyy käyttää useissa paikoissa, TypeScript vaikuttaa vain asialta, josta aluksi pidit, mutta nyt se on vain hankala asia käsitellä. Tämän ongelman ratkaisemiseksi määrittelemme rajapintatyypin käyttämällä interface
-avainsanaa.
Edellisessä esimerkissä määrittelimme rajapintatyypin Person
, joka kuvaa olion muotoa, mutta tällä kertaa meillä on nimi, jota voimme käyttää viitatessamme kyseiseen tyyppiin. Olemme käyttäneet tätä tyyppiä annotoidaksemme ross
-muuttujan sekä getPersonIfo
-funktion person
-argumentin getPersonIfo
. Tämä ilmoittaa TypeScriptille, että se validoi nämä oliot muotoa Person
vastaan.
Miksi käyttää rajapintaa?
Rajapintatyyppi voi olla tärkeä tietyn muodon pakottamiseksi. Tyypillisesti JavaScriptissä luotamme suoritusaikana sokeasti siihen, että objekti sisältää aina tietyn ominaisuuden ja että tuolla ominaisuudella on aina tietyn tyyppinen arvo, kuten esimerkiksi {age: 21, ...}
.
Kun alamme itse asiassa suorittaa operaatioita tuolle ominaisuudelle tarkistamatta ensin, onko kyseinen ominaisuus olemassa objektissa tai onko sen arvo se, mitä odotimme, asiat voivat mennä pieleen, ja se voi jättää sovelluksen käyttökelvottomaksi jälkeenpäin. Esimerkiksi {age: '21', ...}
, tässä age
arvo on string
.
Rajapinnat tarjoavat turvallisen mekanismin tällaisten skenaarioiden käsittelemiseen kääntämisaikana. Jos käytät vahingossa ominaisuutta objektissa, jota ei ole olemassa, tai käytät ominaisuuden arvoa laittomassa operaatiossa, TypeScript-kääntäjä ei käännä ohjelmaasi. Katsotaanpa esimerkki.
Yllä olevassa esimerkissä yritämme käyttää name
-ominaisuutta _student
-argumentin name
sisällä printStudent
-funktiossa. Koska _student
-argumentti on Student
-rajapinnan tyyppi, TypeScript-kääntäjä heittää virheen kääntämisen aikana, koska tätä ominaisuutta ei ole Student
-rajapinnassa.
Vaikka 100 — _student.firstName
ei ole kelvollinen operaatio, koska firstName
-ominaisuus on string
-tyyppi ja viimeksi kun tarkistin, et voi vähentää string
:aa number
:sta number
on JavaScript (tuloksena NaN
).
Ylläolevassa esimerkissä olemme käyttäneet perinteistä tapaa kirjoittaa funktiotyyppi getSalary
-kentälle. Voit kuitenkin käyttää samaan myös funktiosyntaksia ilman runkoa, jota käytetään yleensä rajapinnoissa.
interface Student {
firstName: string;
lastName: string;
age: number;
getSalary(base: number): number;
};
Vaihtoehtoiset ominaisuudet
Joskus objektilla on oltava ominaisuus, joka pitää sisällään tietyn tietotyypin tietoja, mutta kyseistä ominaisuutta ei ole pakko olla objektilla. Tämä on samanlaista kuin edellisellä oppitunnilla opitut valinnaiset funktioparametrit.
Tällaisia ominaisuuksia kutsutaan valinnaisiksi ominaisuuksiksi. Rajapinta voi sisältää valinnaisia ominaisuuksia, ja käytämme ?:Type
-annotaatiota niiden esittämiseen, aivan kuten valinnaisten funktioparametrien esittämiseen.
Ylläolevassa esimerkissä rajapinnalla Student
on ominaisuus age
, joka on valinnainen. Jos age
-ominaisuus kuitenkin annetaan, sen arvon on oltava tyyppiä number
.
Objektin ross
tapauksessa, joka on Student
-rajapinnan tyyppi, emme ole antaneet arvoa age
-ominaisuudelle, mikä on laillista, mutta monica
-objektin monica
tapauksessa olemme antaneet age
-ominaisuuden, mutta sen arvo on string
, mikä ei ole laillista. Näin ollen TypeScript-kääntäjä heittää virheen.
Virhe saattaa vaikuttaa oudolta, mutta siinä on itse asiassa järkeä. Jos age
-ominaisuutta ei ole objektilla, object.age
palauttaa undefined
, joka on tyypiltään undefined
. Jos se on olemassa, arvon on oltava tyyppiä number
.
Siten age
-ominaisuuden arvo voi olla joko tyyppiä undefined
tai number
, joka TypeScriptissä esitetään union-syntaksilla number | undefined
.
💡 Opettelemme tyypin unionit Type System -oppitunnilla.
Vapaavalintaiset ominaisuudet aiheuttavat kuitenkin vakavia ongelmia ohjelman suorituksessa. Kuvitellaanpa, että käytämme age
-ominaisuutta aritmeettisessa operaatiossa, mutta sen arvo on undefined
. Tämä on eräänlainen vakava ongelma.
Mutta hyvä asia on se, että TypeScript-kääntäjä ei salli laittomien operaatioiden suorittamista valinnaiselle ominaisuudelle, koska sen arvo voi olla undefined
.
Yllä olevassa esimerkissä suoritamme aritmeettisen operaation age
-ominaisuudelle, mikä on laitonta, koska tämän ominaisuuden arvo voi olla number
tai undefined
ajonaikana. Aritmeettisten operaatioiden suorittaminen undefined
:lle johtaa tulokseen NaN
(ei luku).
💡 Edellä mainittua ohjelmaa varten meidän oli kuitenkin tp asetettava
--strictNullChecks
-lippu arvoonfalse
, joka on TypeScript-kääntäjän lippu. Jos annamme tämän vaihtoehdon, yllä oleva ohjelma kääntyy aivan hyvin.
Välttääksemme tämän virheen tai varoituksen meidän on nimenomaisesti kerrottava TypeScript-kääntäjälle, että tämä ominaisuus on tyyppi number
eikä number
tai undefined
. Tätä varten käytämme type assertionia (AKA type conversion tai typecasting).
Ylläolevassa ohjelmassa olemme käyttäneet (_student.age as number)
:ää, joka muuntaa _student.age
:n tyypin number | undefined
:stä number
:ksi. Tämä on tapa kertoa TypeScript-kääntäjälle: ”Hei, tämä on numero”. Mutta parempi tapa käsitellä tätä olisi myös tarkistaa, onko _student.age
undefined
ajonaikana ja suorittaa sitten aritmeettinen operaatio.
💡 Opimme tyyppiväittämistä Type System -oppitunnilla.
Funktion tyyppi rajapinnan avulla
Ei vain tavallisen olion muotoa, vaan rajapinta voi kuvata myös funktion allekirjoitusta. Edellisellä oppitunnilla käytimme type alias -tyyppiä kuvaamaan funktiotyyppiä, mutta rajapinnat voivat tehdä sen myös.
interface InterfaceName {
(param: Type): Type;
}
Syntaksi, jolla rajapinta ilmoitetaan funktiotyyppinä, on samanlainen kuin itse funktion allekirjoitus. Kuten yllä olevasta esimerkistä näet, rajapinnan runko sisältää täsmälleen anonyymin funktion allekirjoituksen, ilman runkoa tietenkin. Tässä parametrien nimillä ei ole merkitystä.
Yllä olevassa esimerkissä olemme määritelleet IsSumOdd
rajapinnan, joka määrittelee funktiotyypin, joka hyväksyy kaksi argumenttia, jotka ovat tyypiltään number
, ja joka palauttaa boolean
-arvon. Nyt voit käyttää tätä tyyppiä kuvaamaan funktiota, koska IsSumOdd
-rajapintatyyppi vastaa funktiotyyppiä (x: number, y: number) => boolean
.
Rajapinta, jolla on anonyymi metodisignatuuri, kuvaa funktiota. Mutta funktio on JavaScript-maailmassa myös objekti, mikä tarkoittaa, että voit lisätä ominaisuuksia funktion arvoon aivan kuten objektiin. Siksi on täysin laillista, että voit määritellä mitä tahansa ominaisuuksia funktiotyyppiselle rajapinnalle.
Edellisessä esimerkissä olemme määritelleet LapTimes
-rajapinnan, joka voi sisältää ominaisuuksien nimiä, jotka ovat tyyppiä number
, ja arvoja, jotka ovat tyyppiä number
. Tämä rajapinta voi edustaa tietorakennetta, jota voidaan indeksoida number
-avaimilla, joten array ross
ja objektit monica
ja joey
ovat laillisia.
Objekti rachel
ei kuitenkaan ole LapTimes
-muodon mukainen, koska avain one
on string
ja sitä voidaan käyttää vain string
:n, kuten rachel
:n, avulla eikä minkään muun. Näin ollen TypeScript-kääntäjä heittää virheen kuten yllä on esitetty.
Indeksoitavassa rajapintatyypissä voi olla joitakin ominaisuuksia pakollisia ja joitakin valinnaisia. Tämä voi olla varsin hyödyllistä, kun objektin pitää olla tietyn muotoinen, mutta ei oikeastaan haittaa, jos objektiin tulee ylimääräisiä ja ei-toivottuja ominaisuuksia.
Yllä olevassa esimerkissä olemme määritelleet rajapinnan LapTimes
, jonka on sisällettävä ominaisuus name
arvolla string
ja valinnainen ominaisuus age
arvolla number
. Tyypin LapTimes
objektilla voi olla myös mielivaltaisia ominaisuuksia, joiden avainten on oltava number
ja joiden arvojen on myös oltava number
.
Objekti ross
on kelvollinen LapTimes
-objekti, vaikka sillä ei ole age
-ominaisuutta, koska se on valinnainen. Kuitenkin monica
:llä on age
-ominaisuus, mutta sen arvo on string
, joten se ei ole LapTimes
-rajapinnan mukainen.
Objekti joey
ei myöskään ole LapTimes
-rajapinnan mukainen, koska sillä on gender
-ominaisuus, joka on tyyppi string
. rachel
-objektilla ei ole name
-ominaisuutta, joka vaaditaan LapTimes
-rajapinnassa.
💡 Indeksoitavia tyyppejä käytettäessä on joitain gotchoja, joita meidän on varottava. Ne mainitaan tässä dokumentaatiossa.
Liitännän laajentaminen
Kuten luokat, myös rajapinta voi periä ominaisuuksia toisilta rajapinnoilta. Toisin kuin JavaScriptin luokat, rajapinta voi kuitenkin periä useista rajapinnoista. Käytämme extends
-avainsanaa rajapinnan periyttämiseen.
Perustaessamme rajapintaa lapsirajapinta saa kaikki vanhemman rajapinnan ominaisuudet. Tämä on varsin hyödyllistä, kun useilla rajapinnoilla on yhteinen rakenne ja haluamme välttää koodin päällekkäisyyttä ottamalla yhteiset ominaisuudet ulos yhteiseen rajapintaan, joka voidaan myöhemmin periä.
Yllä olevassa esimerkissä olemme luoneet Student
-rajapinnan, joka perii ominaisuuksia Person
– ja Player
-rajapinnoilta.
Moninkertaiset rajapintadeklaraatiot
Edellisessä kappaleessa opimme, miten rajapinta voi periä toisen rajapinnan ominaisuuksia. Tämä tehtiin extend
-avainsanan avulla. Kun samannimisiä rajapintoja kuitenkin julistetaan samassa moduulissa (tiedostossa), TypeScript sulauttaa niiden ominaisuudet yhteen, kunhan niillä on erilaiset ominaisuuksien nimet tai niiden ristiriitaiset ominaisuustyypit ovat samat.
Ylläolevassa esimerkissämme olemme julistaneet rajapintatietoja useaan kertaan. 36961>
Person
-rajapintadeklaraatio yhdistämällä kaikkien Person
-rajapintadeklaraatioiden ominaisuudet.
💡 Luokkien oppitunnilla olemme oppineet, että
class
deklarioi implisiittisesti rajapinnan ja rajapinta voi laajentaa kyseistä rajapintaa. Jos siis ohjelmassa on luokkaPerson
ja rajapintaPerson
, niin lopullisellaPerson
-tyypillä (rajapinta) on yhdistettyjä ominaisuuksia luokan ja rajapinnan välillä.
Sisäkkäiset rajapinnat
Rajapinnassa voi olla syvälle sisäkkäisiä rakenteita. Alla olevassa esimerkissä Student
-rajapinnan info
-kenttä määrittelee objektin muodon, jolla on firstName
– ja lastName
-ominaisuudet.
Niin ikään on täysin laillista, että rajapinnan kenttä voi olla tyypiltään toisen rajapinnan kenttä. Seuraavassa esimerkissä Student
-rajapinnan info
-kentällä on tyyppi Person
-rajapinta.