Zoeken…


Invoering

Een interface geeft een lijst met velden en functies die kunnen worden verwacht op elke klasse die de interface implementeert. Omgekeerd kan een klasse een interface alleen implementeren als elk veld en elke functie op de interface is opgegeven.

Het primaire voordeel van het gebruik van interfaces is dat het mogelijk maakt om objecten van verschillende typen op een polymorfe manier te gebruiken. Dit komt omdat elke klasse die de interface implementeert ten minste die velden en functies heeft.

Syntaxis

  • interface InterfaceName {
  • parameterName: parameterType;
  • optioneelParameterName ?: parameterType;
  • }

Opmerkingen

Interfaces versus type aliassen

Interfaces zijn goed voor het specificeren van de vorm van een object, bijvoorbeeld voor een persoonsobject dat u zou kunnen specificeren

interface person {
    id?: number;
    name: string;
    age: number;
}

Maar wat als u bijvoorbeeld wilt weergeven hoe een persoon is opgeslagen in een SQL-database? Aangezien elk DB-item bestaat uit een rij met vorm [string, string, number] (dus een reeks van strings of getallen), is er geen manier om dit als een objectvorm weer te geven, omdat de rij geen eigenschappen heeft als zodanig is het gewoon een array.

Dit is een gelegenheid waarbij typen van pas komen. In plaats van op te geven in elke functie die een rijparameterfunctieprocesRow accepteert function processRow(row: [string, string, number]) , kunt u een afzonderlijk alias voor een rij maken en dat vervolgens in elke functie gebruiken:

type Row = [string, string, number];
function processRow(row: Row)

Officiële interfacedocumentatie

https://www.typescriptlang.org/docs/handbook/interfaces.html

Voeg functies of eigenschappen toe aan een bestaande interface

Laten we aannemen dat we een verwijzing hebben naar de JQuery en we willen deze uitbreiden met extra functies van een plug-in die we hebben opgenomen en die geen officiële typedefinitie heeft. We kunnen het eenvoudig uitbreiden door functies die door een plug-in zijn toegevoegd in een afzonderlijke interfaceaangifte met dezelfde JQuery naam te verklaren:

interface JQuery {
  pluginFunctionThatDoesNothing(): void;

  // create chainable function
  manipulateDOM(HTMLElement): JQuery;
}

De compiler voegt alle aangiften met dezelfde naam samen in één - zie aangifte samenvoegen voor meer informatie.

Klasse-interface

Verklaar public variabelen en type methoden in de interface om te definiëren hoe andere typescript-code ermee kan communiceren.

interface ISampleClassInterface {
  sampleVariable: string;

  sampleMethod(): void;
  
  optionalVariable?: string;
}

Hier maken we een klasse die de interface implementeert.

class SampleClass implements ISampleClassInterface {
  public sampleVariable: string;
  private answerToLifeTheUniverseAndEverything: number;

  constructor() {
    this.sampleVariable = 'string value';
    this.answerToLifeTheUniverseAndEverything = 42;
  }

  public sampleMethod(): void {
    // do nothing
  }
  private answer(q: any): number {
    return this.answerToLifeTheUniverseAndEverything;
  }
}

Het voorbeeld laat zien hoe u een interface ISampleClassInterface en een klasse SampleClass die de interface implements .

Uitbreidende interface

Stel dat we een interface hebben:

interface IPerson {
    name: string;
    age: number;

    breath(): void;
}

En we willen een meer specifieke interface met dezelfde eigenschappen van de persoon maken, we kunnen dit doen met behulp van het extends trefwoord:

interface IManager extends IPerson {
    managerId: number;

    managePeople(people: IPerson[]): void;
}

Bovendien is het mogelijk om meerdere interfaces uit te breiden.

Interfaces gebruiken om typen af te dwingen

Een van de belangrijkste voordelen van Typescript is dat het gegevenstypes van waarden afdwingt die u doorgeeft aan uw code om fouten te voorkomen.

Laten we zeggen dat je een datingapp voor huisdieren aanmaakt.

Je hebt deze eenvoudige functie die controleert of twee huisdieren compatibel zijn met elkaar ...

checkCompatible(petOne, petTwo) {
  if (petOne.species === petTwo.species &&
      Math.abs(petOne.age - petTwo.age) <= 5) {
    return true;
  }
}

Dit is volledig functionele code, maar het zou veel te gemakkelijk zijn voor iemand, vooral andere mensen die aan deze applicatie werken en deze functie niet hebben geschreven, om zich niet bewust te zijn dat ze objecten met 'soort' en 'leeftijd' moeten doorgeven eigendommen. Ze kunnen per ongeluk proberen checkCompatible(petOne.species, petTwo.species) en dan achtergelaten worden om de fouten te achterhalen die worden gegenereerd wanneer de functie toegang probeert te krijgen tot petOne.species.species of petOne.species.age!

Een manier waarop we dit kunnen voorkomen, is door de gewenste eigenschappen op te geven voor de huisdierparameters:

checkCompatible(petOne: {species: string, age: number}, petTwo: {species: string, age: number}) {
    //...
} 

In dit geval zal Typescript ervoor zorgen dat alles wat aan de functie wordt doorgegeven eigenschappen voor 'species' en 'age' heeft (het is prima als ze extra eigenschappen hebben), maar dit is een beetje een logge oplossing, zelfs met slechts twee opgegeven eigenschappen. Met interfaces is er een betere manier!

Eerst definiëren we onze interface:

interface Pet {
  species: string;
  age: number;
  //We can add more properties if we choose.
}

Nu hoeven we alleen nog maar het type van onze parameters op te geven als onze nieuwe interface, zo ...

checkCompatible(petOne: Pet, petTwo: Pet) {
  //...
}

... en Typescript zal ervoor zorgen dat de parameters die worden doorgegeven aan onze functie de eigenschappen bevatten die zijn gespecificeerd in de Pet-interface!

Generieke interfaces

Net als klassen kunnen interfaces polymorfe parameters (ook bekend als Generics) ontvangen.

Generieke parameters op interfaces declareren

interface IStatus<U> {
    code: U;
}

interface IEvents<T> {
    list: T[];
    emit(event: T): void;
    getAll(): T[];
}

Hier kunt u zien dat onze twee interfaces enkele generieke parameters nodig hebben, T en U.

Generieke interfaces implementeren

We zullen een eenvoudige klasse maken om de interface IEvents te implementeren.

class State<T> implements IEvents<T> {
    
    list: T[];
    
    constructor() {
        this.list = [];
    }
    
    emit(event: T): void {
        this.list.push(event);
    }
    
    getAll(): T[] {
        return this.list;
    }
    
}

Laten we enkele instanties van onze klasse State maken .

In ons voorbeeld zal de klasse State een generieke status verwerken met behulp van IStatus<T> . Op deze manier kan de interface IEvent<T> ook een IStatus<T> verwerken.

const s = new State<IStatus<number>>();

// The 'code' property is expected to be a number, so:
s.emit({ code: 200 }); // works
s.emit({ code: '500' }); // type error 

s.getAll().forEach(event => console.log(event.code));

Hier onze State klasse wordt getypeerd als ISatus<number> .


const s2 = new State<IStatus<Code>>();

//We are able to emit code as the type Code
s2.emit({ code: { message: 'OK', status: 200 } });

s2.getAll().map(event => event.code).forEach(event => {
    console.log(event.message);
    console.log(event.status);
});

Onze klasse State wordt getypt als IStatus<Code> . Op deze manier kunnen we complexere typen doorgeven aan onze emit-methode.

Zoals u ziet, kunnen generieke interfaces een zeer handig hulpmiddel zijn voor statisch getypte code.

Interfaces gebruiken voor polymorfisme

De belangrijkste reden om interfaces te gebruiken om polymorfisme te bereiken en ontwikkelaars de mogelijkheid te bieden om in de toekomst op hun eigen manier te implementeren door de methoden van de interface te implementeren.

Stel dat we een interface en drie klassen hebben:

interface Connector{
    doConnect(): boolean;
}

Dit is een connectorinterface. Nu zullen we dat implementeren voor Wifi-communicatie.

export class WifiConnector implements Connector{

    public doConnect(): boolean{
        console.log("Connecting via wifi");
        console.log("Get password");
        console.log("Lease an IP for 24 hours");
        console.log("Connected");
        return true
    }

}

Hier hebben we onze concrete klasse genaamd WifiConnector die zijn eigen implementatie heeft. Dit is nu het type Connector .

Nu zijn we het creëren van onze System dat een component heeft Connector . Dit wordt afhankelijkheidsinjectie genoemd.

export class System {
    constructor(private connector: Connector){ #inject Connector type
        connector.doConnect()
    }
}

constructor(private connector: Connector) deze lijn is hier erg belangrijk. Connector is een interface en moet doConnect() . Aangezien Connector een interface is, biedt dit System veel meer flexibiliteit. We kunnen elk type passeren dat Connector interface heeft geïmplementeerd. In de toekomst bereikt ontwikkelaar meer flexibiliteit. De ontwikkelaar wil bijvoorbeeld de Bluetooth-verbindingsmodule toevoegen:

export class BluetoothConnector implements Connector{

    public doConnect(): boolean{
        console.log("Connecting via Bluetooth");
        console.log("Pair with PIN");
        console.log("Connected");
        return true
    }

}

Zie dat Wifi en Bluetooth een eigen implementatie hebben. Er is een andere manier om verbinding te maken. Beide hebben daarom Type Connector geïmplementeerd en zijn nu Type Connector . Zodat we deze allemaal kunnen doorgeven aan de System als de constructorparameter. Dit wordt polymorfisme genoemd. De klasse System is zich er nu niet van bewust of het Bluetooth / Wifi is, zelfs we kunnen een andere communicatiemodule zoals Inferade, Bluetooth5 en wat dan ook toevoegen door slechts een Connector interface te implementeren.

Dit wordt eendtypen genoemd . Connector type Connector is nu dynamisch, omdat doConnect() slechts een tijdelijke aanduiding en ontwikkelaar is die dit als zijn / haar implementeert.

als bij constructor(private connector: WifiConnector) waar WifiConnector een concrete klasse is, wat gebeurt er dan? Dan System class zal strak paar alleen WifiConnector niets anders. Hier heeft de interface ons probleem opgelost door polymorfisme.

Impliciete implementatie en objectvorm

TypeScript ondersteunt interfaces, maar de compiler voert JavaScript uit, wat niet het geval is. Daarom gaan interfaces effectief verloren in de compilatiestap. Dit is de reden waarom typecontrole op interfaces afhankelijk is van de vorm van het object - wat betekent of het object de velden en functies op de interface ondersteunt - en niet of de interface daadwerkelijk is geïmplementeerd of niet.

interface IKickable {
  kick(distance: number): void;
}
class Ball {
  kick(distance: number): void {
    console.log("Kicked", distance, "meters!");
  }
}
let kickable: IKickable = new Ball();
kickable.kick(40);

Dus zelfs als Ball IKickable niet expliciet implementeert, kan een Ball instantie worden toegewezen aan (en gemanipuleerd als) een IKickable , zelfs wanneer het type is opgegeven.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow