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.

(object-shape.ts)

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.

(shape-override.ts)

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.

(shape-override-any.ts)

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ä.

(argumentin-muoto.ts)

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.

(argument-with-interface.ts)

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.

(interface-safety.ts)

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.

(optionaaliset-ominaisuudet.ts)

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.

(optionaaliset-ominaisuudet-safety.ts)

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 arvoon false, 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).

(optional-properties-safety-override.ts)

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ä.

(function-signature.ts)

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.

(funktio-allekirjoitus-metodeilla.ts)

Ylläolevassa esimerkissämme lisäsimme funktiotyyppiselle IsSumOdd rajapinnalle, joka kuvaa funktiotyyppistä funktiotyyppistä rajapintaa,type– ja calculate-ominaisuudet. Käyttämällä Object.assign-metodia yhdistämme type– ja calculate-ominaisuudet funktion arvoon.

Funktiotyyppiset rajapinnat voivat olla hyödyllisiä konstruktorifunktioiden kuvaamisessa. Konstruktorifunktio on samanlainen kuin luokka, jonka tehtävänä on luoda objekteja (instansseja). Meillä oli ES5:een asti vain konstruktorifunktioita jäljittelemään class JavaScriptissä. Siksi TypeScript kääntää luokat konstruktorifunktioiksi, jos kohteena on ES5 tai sen alle.

💡 Jos haluat oppia lisää konstruktorifunktiosta, seuraa tätä artikkelia.

Jos laitamme new-avainsanan ennen anonyymin funktion allekirjoitusta rajapinnassa, se tekee funktiosta konstruoitavan. Se tarkoittaa, että funktiota voidaan kutsua vain new-avainsanalla objektien luomiseksi eikä tavallisella funktiokutsulla. Esimerkki konstruktorifunktiosta näyttää alla olevalta.

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

Onneksi meidän ei tarvitse työskennellä konstruktorifunktioiden kanssa, koska TypeScript tarjoaa class-avainsanan luokan luomiseen, jonka kanssa on paljon helpompi työskennellä kuin konstruktorifunktion kanssa, usko minua. Itse asiassa class on syvällä sisimmässään JavaScriptissä konstruktorifunktio. Kokeile alla olevaa esimerkkiä.

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

Luokka ja konstruktorifunktio ovat yksi ja sama asia. Ainoa ero on se, että class antaa meille rikkaan OOP-syntaksin käytettäväksi. Näin ollen konstruktorifunktion tyyppinen rajapinta edustaa luokkaa.

(function-signature-with-new.ts)

Ylläolevassa esimerkissä olemme määritelleet Animal-luokan, jolla on konstruktorifunktio, joka hyväksyy argumentin, joka on tyyppi string. Voit pitää tätä konstruktorifunktiona, jolla on samanlainen allekirjoitus kuin Animal-konstruktorilla.

AnimalInterface määrittelee konstruktorifunktion, koska siinä on anonyymi funktio etuliitteenä new-avainsanalla. Tämä tarkoittaa, että Animal-luokka kelpaa AnimalInterface:n tyypiksi. Tässä AnimalInterface-rajapintatyyppi vastaa funktiotyyppiä new (sound: string) => any.

createAnimal-funktio createAnimal hyväksyy ctor-argumentin, joka on AnimalInterface-tyyppiä, joten voimme välittää Animal-luokan argumentin arvona. Emme pysty lisäämään Animal-luokan getSound metodisignatuuria AnimalInterface-luokkaan, ja syy selitetään Luokat-oppitunnilla.

Indexoitavat tyypit

Indexoitava objekti on objekti, jonka ominaisuuksia voidaan käyttää indeksisignatuurin avulla, kuten obj. Tämä on oletusarvoinen tapa päästä käsiksi array-elementtiin, mutta voimme tehdä tämän myös objektille.

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

Objektillasi voi joskus olla mielivaltainen määrä ominaisuuksia ilman mitään tiettyä muotoa. Siinä tapauksessa voit vain käyttää object-tyyppiä. Tämä object-tyyppi määrittelee kuitenkin minkä tahansa arvon, joka ei ole number, string, boolean, symbol, null tai undefined, kuten perustyypit-oppitunnilla käsiteltiin.

Jos meidän on tarkkaan tarkistettava, onko jokin arvo tavallinen JavaScript-objekti, meillä voi olla ongelma. Tämä voidaan ratkaista käyttämällä rajapintatyyppiä, jossa on ominaisuuden nimen indeksisignatuuri.

interface SimpleObject {
: any;
}

SimpleObject-rajapinta määrittelee muodon objektille, jolla on string avaimia, joiden arvot voivat olla any tietotyyppiä. Tässä key-ominaisuuden nimeä käytetään vain sijoittimena, koska se on suljettu hakasulkeisiin.

(indexable-type-object.ts)

Yllä olevaan esimerkkiin määriteltiin ross– ja monica-oliot, jotka ovat tyypiltään rajapintatyyppiä SimpleObject. Koska nämä objektit sisältävät string-avaimia ja any-tietotyypin arvoja, se on täysin laillista.

Mikäli olet hämmentynyt monica:n avaimesta 1, joka on tyypiltään number, tämä on laillista, koska JavaScriptissä objekti- tai array-objektit voidaan indeksoida number– tai string-avainten avulla, kuten alla on esitetty.

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

Mikäli avainten tyyppiä ja arvoja on tarpeen täsmentää tarkemmin, voimme varmasti tehdä sen myös. Voimme esimerkiksi halutessamme määritellä indeksoitavan rajapintatyypin, jonka avaimet ovat tyyppiä number ja arvot tyyppiä number.

💡 Indeksisignatuurin avaintyypin on oltava joko string tai number.

(indexable-type-number.ts)

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.

(indexable-type-required-properties.ts)

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.

(extend-interface.ts)

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ä.

(extend-multiple-interfaces.ts)

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.

(sulautetturajapintatiedosto.ts)

Ylläolevassa esimerkissämme olemme julistaneet rajapintatietoja useaan kertaan. 36961>

. Näin saadaan yksi 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 luokka Person ja rajapinta Person, niin lopullisella Person-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.

(nested-shape.ts)

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.

(nested-interface.ts)

Vastaa

Sähköpostiosoitettasi ei julkaista.