Suche…


Einführung

Eine Schnittstelle gibt eine Liste von Feldern und Funktionen an, die in jeder Klasse erwartet werden können, die die Schnittstelle implementiert. Umgekehrt kann eine Klasse eine Schnittstelle nicht implementieren, sofern nicht jedes Feld und jede Funktion auf der Schnittstelle angegeben ist.

Der Hauptvorteil der Verwendung von Schnittstellen besteht darin, dass Objekte unterschiedlicher Art auf polymorphe Weise verwendet werden können. Dies liegt daran, dass jede Klasse, die die Schnittstelle implementiert, mindestens diese Felder und Funktionen aufweist.

Syntax

  • Schnittstelle Schnittstellenname {
  • Parametername: Parametertyp;
  • optionalParameterName ?: parameterType;
  • }

Bemerkungen

Schnittstellen vs. Typ-Aliase

Schnittstellen sind gut geeignet, um die Form eines Objekts anzugeben, z. B. für ein Personenobjekt, das Sie angeben könnten

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

Was aber, wenn Sie die Art und Weise darstellen möchten, wie eine Person in einer SQL-Datenbank gespeichert ist? Da jeder DB-Eintrag aus einer Zeile mit einer Form [string, string, number] (also einem Array von Strings oder Zahlen) besteht, können Sie dies nicht als Objekt-Shape darstellen, da die Zeile keine Eigenschaften hat als solches ist es nur ein Array.

Dies ist eine Gelegenheit, bei der Typen nützlich sind. Anstatt in jeder Funktion anzugeben, die eine function processRow(row: [string, string, number]) akzeptiert, können Sie einen separaten Typalias für eine Zeile erstellen und diesen dann in jeder Funktion verwenden:

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

Offizielle Schnittstellendokumentation

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

Fügen Sie einer vorhandenen Schnittstelle Funktionen oder Eigenschaften hinzu

Nehmen wir an, wir haben einen Verweis auf die JQuery Typdefinition und möchten diese erweitern, um zusätzliche Funktionen von einem Plugin zu haben, das wir hinzugefügt haben und für das keine offizielle Typdefinition vorhanden ist. Wir können es einfach erweitern, indem Sie Funktionen deklarieren, die durch Plugin in einer separaten Schnittstellendeklaration mit demselben JQuery Namen hinzugefügt wurden:

interface JQuery {
  pluginFunctionThatDoesNothing(): void;

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

Der Compiler führt alle Deklarationen mit demselben Namen zu einer zusammen. Weitere Informationen finden Sie unter Zusammenführen von Deklarationen .

Klassenschnittstelle

Deklarieren Sie public Variablen und Methodentypen in der Schnittstelle, um festzulegen, wie anderer Typoscript-Code damit interagieren kann.

interface ISampleClassInterface {
  sampleVariable: string;

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

Hier erstellen wir eine Klasse, die die Schnittstelle implementiert.

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;
  }
}

Das Beispiel zeigt, wie Sie eine Schnittstelle ISampleClassInterface und eine Klasse SampleClass , implements die Schnittstelle implements .

Schnittstelle erweitern

Angenommen, wir haben eine Schnittstelle:

interface IPerson {
    name: string;
    age: number;

    breath(): void;
}

Und wir wollen mehr spezifische Schnittstelle schaffen , die die gleichen Eigenschaften der Person hat, können wir es tun mit dem extends Stichwort:

interface IManager extends IPerson {
    managerId: number;

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

Zusätzlich können mehrere Schnittstellen erweitert werden.

Verwenden von Schnittstellen zum Erzwingen von Typen

Typische Vorteile von Typescript sind die Erzwingung von Datentypen von Werten, die Sie im Code übergeben, um Fehler zu vermeiden.

Nehmen wir an, Sie machen eine Pet-Dating-Bewerbung.

Sie haben diese einfache Funktion, die prüft, ob zwei Haustiere miteinander kompatibel sind ...

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

Dies ist vollständig funktionaler Code, aber es wäre für jemanden viel zu einfach, vor allem für andere Personen, die an dieser Anwendung arbeiten, die diese Funktion nicht geschrieben haben, dass sie nicht wissen, dass sie Objekte mit 'Spezies' und 'Alter' übergeben sollen. Eigenschaften. Sie versuchen möglicherweise fehlerhaft checkCompatible(petOne.species, petTwo.species) und müssen dann die Fehler herausfinden, die ausgelöst werden, wenn die Funktion auf petOne.species.species oder petOne.species.age zugreift.

Eine Möglichkeit, dies zu verhindern, besteht darin, die gewünschten Eigenschaften für die Tierparameter festzulegen:

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

In diesem Fall stellt Typescript sicher, dass alles, was an die Funktion übergeben wird, Eigenschaften wie 'Spezies' und 'Alter' hat (es ist in Ordnung, wenn sie zusätzliche Eigenschaften haben). Dies ist jedoch eine etwas unhandliche Lösung, selbst wenn nur zwei Eigenschaften angegeben sind. Mit Schnittstellen gibt es einen besseren Weg!

Zuerst definieren wir unsere Schnittstelle:

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

Jetzt müssen wir nur noch die Art unserer Parameter als unsere neue Schnittstelle angeben, wie so ...

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

... und Typescript stellen sicher, dass die an unsere Funktion übergebenen Parameter die in der Pet-Schnittstelle angegebenen Eigenschaften enthalten!

Generische Schnittstellen

Schnittstellen können wie Klassen auch polymorphe Parameter (auch bekannt als Generics) erhalten.

Generische Parameter für Schnittstellen deklarieren

interface IStatus<U> {
    code: U;
}

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

Hier können Sie sehen, dass unsere beiden Schnittstellen einige generische Parameter, T und U, haben .

Generische Schnittstellen implementieren

Wir erstellen eine einfache Klasse, um die Schnittstelle IEvents zu implementieren.

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

Lassen Sie uns einige Instanzen unserer State- Klasse erstellen.

In unserem Beispiel behandelt die State Klasse einen generischen Status mit IStatus<T> . Auf diese Weise wird die Schnittstelle IEvent<T> auch einen IStatus<T> .

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 wird unsere State Klasse als ISatus<number> eingegeben.


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);
});

Unsere State Klasse wird als IStatus<Code> eingegeben. Auf diese Weise können wir komplexere Typen an unsere Emit-Methode übergeben.

Wie Sie sehen, können generische Schnittstellen ein sehr nützliches Werkzeug für statisch typisierten Code sein.

Verwendung von Schnittstellen für Polymorphismus

Der Hauptgrund, Schnittstellen zu verwenden, um Polymorphie zu erreichen, und Entwicklern die Implementierung der Methoden auf eigene Weise in der Zukunft zu ermöglichen.

Angenommen, wir haben eine Schnittstelle und drei Klassen:

interface Connector{
    doConnect(): boolean;
}

Dies ist eine Konnektorschnittstelle. Jetzt werden wir das für die WLAN-Kommunikation implementieren.

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 haben wir unsere konkrete Klasse namens WifiConnector , die eine eigene Implementierung hat. Dies ist jetzt Typ Connector .

Jetzt erstellen wir unser System mit einem Komponenten- Connector . Dies wird als Abhängigkeitsinjektion bezeichnet.

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

constructor(private connector: Connector) Diese Linie ist hier sehr wichtig. Connector ist eine Schnittstelle und muss doConnect() . Als Connector ist eine Schnittstelle , dieses Klasse - System hat viel mehr Flexibilität. Wir können jeden Typ übergeben, der eine Connector Schnittstelle implementiert hat. In Zukunft erreicht der Entwickler mehr Flexibilität. Zum Beispiel möchte der Entwickler nun das Bluetooth-Verbindungsmodul hinzufügen:

export class BluetoothConnector implements Connector{

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

}

Stellen Sie sicher, dass Wifi und Bluetooth eine eigene Implementierung haben. Es gibt eine andere Art der Verbindung. Doch damit haben beide implementiert Typ Connector die sind jetzt Typ Connector . Damit wir diese als Konstruktorparameter an die System Klasse übergeben können. Dies wird als Polymorphismus bezeichnet. Der Klasse System ist jetzt nicht bekannt, ob es sich um Bluetooth / Wifi handelt, selbst wenn wir ein weiteres Kommunikationsmodul wie Inferade, Bluetooth5 und andere Module hinzufügen können, indem Sie lediglich die Connector Schnittstelle implementieren.

Dies wird als Duck-Typing bezeichnet . Connector ist jetzt dynamisch, da doConnect() nur ein Platzhalter ist und der Entwickler dies als seinen eigenen implementiert.

Wenn beim constructor(private connector: WifiConnector) wo WifiConnector eine konkrete Klasse ist, was wird passieren? Dann System - Klasse fest Paar nur mit WifiConnector nichts anderes. Hier löste das Interface unser Problem durch Polymorphismus.

Implizite Implementierung und Objektform

TypeScript unterstützt Interfaces, der Compiler gibt jedoch JavaScript aus, was nicht der Fall ist. Daher gehen Schnittstellen beim Kompilieren effektiv verloren. Deshalb hängt die Typüberprüfung von Schnittstellen von der Form des Objekts ab - dh, ob das Objekt die Felder und Funktionen der Schnittstelle unterstützt - und nicht davon, ob die Schnittstelle tatsächlich implementiert ist oder nicht.

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);

Auch wenn Ball IKickable nicht explizit implementiert, kann eine Ball Instanz einem IKickable zugewiesen (und als IKickable , selbst wenn der Typ angegeben wird.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow