Een interface is een vorm van een object. Een standaard JavaScript-object is een map van key:value-paren. JavaScript object sleutels zijn in bijna alle gevallen strings en hun waarden zijn alle ondersteunde JavaScript waarden (primitief of abstract).

Een interface vertelt de TypeScript compiler over de namen van eigenschappen die een object kan hebben en hun overeenkomstige waardetypes. Daarom is een interface een type en is het een abstract type omdat het is samengesteld uit primitieve types.

Wanneer we een object definiëren met eigenschappen (sleutels) en waarden, creëert TypeScript een impliciete interface door te kijken naar de namen van de eigenschappen en het datatype van hun waarden in het object. Dit gebeurt vanwege de type-inferentie.

(object-shape.ts)

In het bovenstaande voorbeeld hebben we een object student gemaakt met firstName, lastName, age en getSalary velden en een aantal initiële waarden toegewezen. Met behulp van deze informatie maakt TypeScript een impliciet interface type voor student.

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

Een interface is net als een object, maar het bevat alleen de informatie over object eigenschappen en hun typen. We kunnen ook een interface-type maken en het een naam geven, zodat we het kunnen gebruiken om objectwaarden te annoteren, maar hier heeft deze interface geen naam omdat hij impliciet is gemaakt. Je kunt dit vergelijken met het functietype in de vorige les dat eerst impliciet was gecreëerd en daarna hebben we een functietype expliciet gemaakt met behulp van type alias.

Laten we eens proberen om te rommelen met de objecteigenschappen nadat het is gedefinieerd.

(shape-override.ts)

Zoals je in het bovenstaande voorbeeld kunt zien, onthoudt TypeScript de vorm van een object omdat het type ross de impliciete interface is. Als we proberen de waarde van een eigenschap te overschrijven met een waarde van een ander type dan wat in de interface gespecificeerd is of als we proberen een nieuwe eigenschap toe te voegen die niet in de interface gespecificeerd is, dan zal de TypeScript compiler het programma niet compileren.

Als je wilt dat een object in principe elke eigenschap heeft, dan kun je expliciet een waarde markeren any en de TypeScript compiler zal het type niet afleiden uit de toegewezen object waarde. Er zijn andere, betere manieren om dit te bereiken en die zullen we in dit artikel bespreken.

(shape-override-any.ts)

Interface Declaration

Hoewel de impliciete interface die we tot nu toe gezien hebben technisch gezien een type is, is het niet expliciet gedefinieerd. Zoals besproken, is een interface niets anders dan de vorm die een object kan aannemen. Als je een functie hebt die een argument accepteert dat een object zou moeten zijn, maar van een bepaalde vorm, dan moeten we dat argument (parameter) annoteren met een interfacetype.

(argument-met-vorm.ts)

In het bovenstaande voorbeeld hebben we een functie getPersonInfo gedefinieerd die een objectargument accepteert dat firstName, lastName, age en getSalary velden van gespecificeerde gegevenstypen heeft. Merk op dat we een object dat namen van eigenschappen en hun overeenkomstige types bevat als type hebben gebruikt met behulp van de :<type> annotatie. Dit is een voorbeeld van een anonieme interface, omdat de interface geen naam heeft, hij werd inline gebruikt.

Dit alles lijkt een beetje ingewikkeld om te verwerken. Als het ross object ingewikkelder wordt en het moet op meerdere plaatsen gebruikt worden, dan lijkt TypeScript gewoon een ding dat je aanvankelijk leuk vond maar nu gewoon een lastig ding om mee om te gaan. Om dit probleem op te lossen definiëren we een interface type met behulp van interface keyword.

(argument-with-interface.ts)

In het bovenstaande voorbeeld hebben we een interface Person gedefinieerd die de vorm van een object beschrijft, maar deze keer hebben we een naam die we kunnen gebruiken om naar dit type te verwijzen. Wij hebben dit type gebruikt om de variabele ross te annoteren, evenals het argument person van de functie getPersonIfo. Dit zal TypeScript informeren om deze entiteiten te valideren tegen de vorm van Person.

Waarom een interface gebruiken?

Interface type kan belangrijk zijn om een bepaalde vorm af te dwingen. Typisch in JavaScript, stellen we blind vertrouwen in runtime dat een object altijd een bepaalde eigenschap zal bevatten en dat die eigenschap altijd een waarde van een bepaald type zal hebben, zoals {age: 21, ...} als voorbeeld.

Wanneer we daadwerkelijk beginnen met het uitvoeren van bewerkingen op die eigenschap zonder eerst te controleren of die eigenschap bestaat op het object of dat de waarde ervan is wat we verwachtten, kunnen dingen misgaan en het kan uw toepassing onbruikbaar achterlaten na afloop. Bijvoorbeeld, {age: '21', ...}, hier age waarde is een string.

Interfaces bieden een veilig mechanisme om met dergelijke scenario’s om te gaan tijdens het compileren. Als je per ongeluk een eigenschap gebruikt op een object dat niet bestaat of de waarde van een eigenschap gebruikt in een illegale bewerking, zal de TypeScript compiler je programma niet compileren. Laten we eens een voorbeeld bekijken.

(interface-safety.ts)

In het bovenstaande voorbeeld proberen we de name eigenschap van het _student argument te gebruiken binnen de printStudent functie. Aangezien het _student argument een type is van de Student interface, geeft de TypeScript compiler een foutmelding tijdens het compileren omdat deze eigenschap niet bestaat in de Student interface.

Ook 100 — _student.firstName is geen geldige bewerking omdat de firstName eigenschap een type is van string en de laatste keer dat ik heb gekeken, kun je in JavaScript geen string van een number aftrekken (resulteert in NaN).

In het bovenstaande voorbeeld hebben we de traditionele manier gebruikt om functietype te schrijven voor het getSalary veld. U kunt echter ook functie syntax gebruiken zonder de body voor hetzelfde, die over het algemeen wordt gebruikt in interfaces.

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

Optionele eigenschappen

Soms moet een object een eigenschap hebben die gegevens van een bepaald gegevenstype bevat, maar het is niet verplicht om die eigenschap op het object te hebben. Dit is vergelijkbaar met de optionele functieparameters die we in de vorige les hebben geleerd.

Dergelijke eigenschappen worden optionele eigenschappen genoemd. Een interface kan optionele eigenschappen bevatten en we gebruiken de annotatie ?:Type om ze weer te geven, net als de optionele functieparameters.

(optional-properties.ts)

In het bovenstaande voorbeeld heeft de interface Student de eigenschap age, die optioneel is. Als de age eigenschap echter wordt opgegeven, moet deze een waarde hebben van het type number.

In het geval van het ross object, dat een type is van de Student interface, hebben we geen waarde opgegeven voor de age eigenschap die legaal is, maar in het geval van monica hebben we de age eigenschap wel opgegeven, maar de waarde is string wat niet legaal is. Daarom geeft de TypeScript compiler een fout.

De fout lijkt misschien vreemd, maar het is eigenlijk logisch. Als de age eigenschap niet bestaat op een object, zal de object.age undefined teruggeven wat een type is van undefined. Als deze wel bestaat, dan moet de waarde van het type number zijn.

Dus de waarde van de age eigenschap kan ofwel van het type undefined zijn of van het type number dat in TypeScript wordt weergegeven met union syntax number | undefined.

💡 We zullen type unions leren in een Type System les.

Echter, optionele eigenschappen leveren serieuze problemen op tijdens de uitvoering van het programma. Stel dat we de eigenschap age gebruiken in een rekenkundige bewerking, maar de waarde ervan is undefined. Dit is een ernstig probleem.

Maar het goede is dat de TypeScript compiler niet toestaat dat er illegale bewerkingen worden uitgevoerd op een optionele eigenschap, omdat de waarde undefined kan zijn.

(optional-properties-safety.ts)

In het bovenstaande voorbeeld voeren we een rekenkundige bewerking uit op de eigenschap age, wat illegaal is omdat de waarde van deze eigenschap in de runtime number of undefined kan zijn. Het uitvoeren van rekenkundige bewerkingen op undefined resulteert in NaN (geen getal).

💡 Voor bovenstaand programma moesten we echter de --strictNullChecks vlag op false zetten, wat een TypeScript compiler vlag is. Als we deze optie wel instellen, compileert bovenstaand programma prima.

Om deze foutmelding of waarschuwing te vermijden, moeten we de TypeScript compiler expliciet vertellen dat deze eigenschap een type is van number en niet van number of undefined. Hiervoor gebruiken we type assertion (AKA type conversie of typecasting).

(optional-properties-safety-override.ts)

In het bovenstaande programma hebben we (_student.age as number) gebruikt dat het type van _student.age converteert van number | undefined naar number. Dit is een manier om TypeScript compiler te vertellen, “Hey, dit is een getal”. Maar een betere manier om hiermee om te gaan zou zijn om in runtime ook te controleren of _student.age undefined is en dan de rekenkundige bewerking uit te voeren.

💡 We zullen leren over type assertions in een Type System les.

Functietype met behulp van een interface

Niet alleen de vorm van een gewoon object, maar een interface kan ook de signatuur van een functie beschrijven. In de vorige les hebben we type alias gebruikt om een functietype te beschrijven, maar interfaces kunnen dat ook.

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

De syntaxis om een interface als functietype te declareren is vergelijkbaar met de functiehandtekening zelf. Zoals je in het voorbeeld hierboven kunt zien, bevat de body van de interface de exacte signature van een anonieme functie, zonder de body natuurlijk. Hier doen parameternamen er niet toe.

(function-signature.ts)

In het bovenstaande voorbeeld hebben we IsSumOdd interface gedefinieerd die een functietype definieert dat twee argumenten van het type number accepteert en een boolean waarde retourneert. Nu kunt u dit type gebruiken om een functie te beschrijven, omdat het IsSumOdd-interfacetype gelijkwaardig is aan functietype (x: number, y: number) => boolean.

Een interface met een anonieme method-handtekening beschrijft een functie. Maar een functie in het JavaScript rijk is ook een object, wat betekent dat je eigenschappen kunt toevoegen aan een functie waarde net als een object. Daarom is het volkomen legaal dat u willekeurige eigenschappen kunt definiëren op een interface van het type functie.

(function-signature-with-methods.ts)

In het bovenstaande voorbeeld hebben we type en calculate eigenschappen toegevoegd op de interface IsSumOdd die een functie beschrijft. Met behulp van de methode Object.assign voegen we type en calculate eigenschappen samen met een functiewaarde.

Interfaces van het functietype kunnen nuttig zijn om constructorfuncties te beschrijven. Een constructor functie is vergelijkbaar met een klasse die tot taak heeft objecten (instanties) te maken. We hadden alleen constructor functies tot ES5 om een class in JavaScript na te bootsen. Daarom compileert TypeScript klassen naar constructor-functies als je ES5 of lager als doel hebt.

💡 Als je meer wilt leren over constructor-functies, volg dan dit artikel.

Als we new sleutelwoord voor anonieme functiehandtekening in de interface zetten, maakt dat de functie constructible. Dat betekent dat de functie alleen kan worden aangeroepen met het new sleutelwoord om objecten te genereren en niet met een gewone functie-aanroep. Een voorbeeld van een constructor functie ziet er als volgt uit.

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

Gelukkig hoeven we niet met constructor functies te werken omdat TypeScript het class sleutelwoord biedt om een klasse te maken die veel gemakkelijker is om mee te werken dan een constructor functie, geloof me. In feite is een class diep in de kern een constructor functie in JavaScript. Probeer het onderstaande voorbeeld maar eens.

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

Een klasse en een constructor functie zijn een en hetzelfde ding. Het enige verschil is dat de class ons een rijke OOP syntax geeft om mee te werken. Vandaar dat een interface van een constructorfunctietype een klasse vertegenwoordigt.

(function-signature-with-new.ts)

In het bovenstaande voorbeeld hebben we de klasse Animal gedefinieerd met een constructorfunctie die een argument van het type string accepteert. U kunt dit beschouwen als een constructorfunctie die een soortgelijke signatuur heeft als de Animal constructor.

De AnimalInterface definieert een constructorfunctie, aangezien het anonieme functie heeft voorafgegaan door het new sleutelwoord. Dit betekent dat de Animal klasse in aanmerking komt om een type van AnimalInterface te zijn. Hier is AnimalInterface interface type gelijkwaardig aan het functietype new (sound: string) => any.

De createAnimal functie accepteert ctor argument van AnimalInterface type, dus kunnen we Animal klasse als argumentwaarde doorgeven. We zullen niet in staat zijn om getSound methode handtekening van de Animal klasse toe te voegen in AnimalInterface en de reden wordt uitgelegd in de Klassen les.

Indexeerbare Types

Een indexeerbaar object is een object waarvan de eigenschappen kunnen worden benaderd met behulp van een index handtekening zoals obj. Dit is de standaard manier om een array-element te benaderen, maar we kunnen dit ook doen voor het object.

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

Op sommige momenten kan uw object een willekeurig aantal eigenschappen hebben zonder een bepaalde vorm. In dat geval kunt u gewoon het object type gebruiken. Dit object-type definieert echter elke waarde die niet number, string, boolean, symbol, null, of undefined is, zoals besproken in de basis-typen-les.

Als we strikt moeten controleren of een waarde een gewoon JavaScript-object is, kunnen we een probleem hebben. Dit kan worden opgelost met behulp van een interface-type met een index-handtekening voor de naam van de eigenschap.

interface SimpleObject {
: any;
}

De SimpleObject-interface definieert de vorm van een object met string sleutels waarvan de waarden any datatype kunnen zijn. Hier wordt de key eigenschapsnaam slechts gebruikt als plaatshouder, aangezien deze tussen vierkante haken staat.

(indexable-type-object.ts)

In het bovenstaande voorbeeld hebben we ross en monica object gedefinieerd van het type SimpleObject interface. Aangezien deze objecten string-sleutels en waarden van het gegevenstype any bevatten, is dit volkomen legaal.

Als u in de war bent over de sleutel 1 in monica, die een type is van number, is dit legaal omdat object- of array-items in JavaScript kunnen worden geïndexeerd met number– of string-sleutels, zoals hieronder wordt weergegeven.

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

Als we preciezer moeten zijn over het type sleutels en hun waarden, kunnen we dat zeker ook doen. We kunnen bijvoorbeeld een indexeerbaar interfacetype definiëren met sleutels van het type number en waarden van het type number als we dat willen.

💡 Het sleuteltype van een indexhandtekening moet of string of number zijn.

(indexeerbaar-type-nummer.ts)

In het bovenstaande voorbeeld hebben we een LapTimes-interface gedefinieerd die eigenschapsnamen van het type number en waarden van het type number kan bevatten. Deze interface kan een gegevensstructuur vertegenwoordigen die kan worden geïndexeerd met number-sleutels, dus array ross en objecten monica en joey zijn legaal.

Het object rachel voldoet echter niet aan de vorm van LapTimes, omdat sleutel one een string is en alleen kan worden benaderd met string, zoals rachel en niets anders. Daarom zal de TypeScript compiler een foutmelding geven zoals hierboven getoond.

Het is mogelijk om sommige eigenschappen verplicht en sommige optioneel te hebben in een indexeerbaar interface type. Dit kan heel handig zijn als we een object een bepaalde vorm moeten geven, maar het maakt echt niet uit als we extra en ongewenste eigenschappen in het object krijgen.

(indexable-type-required-properties.ts)

In het bovenstaande voorbeeld hebben we een interface LapTimes gedefinieerd die eigenschap name met string waarde en optionele eigenschap age met number waarde moet bevatten. Een object van het type LapTimes kan ook willekeurige eigenschappen hebben waarvan de sleutels number moeten zijn en waarvan de waarden ook number moeten zijn.

Het object ross is een geldig LapTimes object, ook al heeft het niet de age eigenschap, omdat deze optioneel is. monica heeft echter wel de age eigenschap, maar de waarde is string, zodat het niet voldoet aan de LapTimes interface.

Het joey object voldoet ook niet aan de LapTimes interface, omdat het een gender eigenschap heeft, die een type is van string. Het rachel object heeft geen name eigenschap die vereist is in de LapTimes interface.

💡 Er zijn enkele gotchas waar we op moeten letten bij het gebruik van indexeerbare types. Deze worden in deze documentatie vermeld.

Extending Interface

Zoals klassen kan een interface eigenschappen van andere interfaces overerven. Echter, in tegenstelling tot klassen in JavaScript, kan een interface erven van meerdere interfaces. We gebruiken het sleutelwoord extends om een interface te erven.

(extend-interface.ts)

Door een interface uit te breiden, krijgt de kind-interface alle eigenschappen van de ouder-interface. Dit is heel nuttig wanneer meerdere interfaces een gemeenschappelijke structuur hebben en we code-duplicatie willen vermijden door de gemeenschappelijke eigenschappen in een gemeenschappelijke interface onder te brengen die later kan worden geërfd.

(extend-multiple-interfaces.ts)

In het bovenstaande voorbeeld hebben we een Student interface gemaakt die eigenschappen erft van de Person en Player interface.

Multiple Interface Declarations

In de vorige sectie hebben we geleerd hoe een interface de eigenschappen van een andere interface kan erven. Dit werd gedaan met behulp van het extend sleutelwoord. Wanneer interfaces met dezelfde naam echter binnen dezelfde module (bestand) worden gedeclareerd, voegt TypeScript hun eigenschappen samen zolang ze verschillende eigenschapsnamen hebben of hun conflicterende eigenschapstypes hetzelfde zijn.

(merged-interface.ts)

In het bovenstaande voorbeeld hebben we interface Person meerdere keren gedeclareerd. Dit zal resulteren in een enkele Person-interface declaratie door de eigenschappen van alle Person-interface declaraties samen te voegen.

💡 In de Classes les hebben we geleerd dat een class impliciet een interface declareert en dat een interface die interface kan uitbreiden. Dus als een programma een klasse Person en een interface Person heeft, dan zal het uiteindelijke Person type (interface) samengevoegde eigenschappen hebben tussen de klasse en de interface.

Nested Interfaces

Een interface kan diep geneste structuren hebben. In het onderstaande voorbeeld definieert het info-veld van de Student-interface de vorm van een object met firstName en lastName-eigenschappen.

(nested-shape.ts)

Ook is het volkomen legaal dat een veld van een interface het type heeft van een andere interface. In het volgende voorbeeld heeft het info-veld van de Student-interface het type van Person-interface.

(nested-interface.ts)

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.