Recherche…


Introduction

Une interface spécifie une liste de champs et de fonctions pouvant être attendus sur toute classe implémentant l'interface. À l'inverse, une classe ne peut implémenter une interface que si elle possède tous les champs et toutes les fonctions spécifiés sur l'interface.

Le principal avantage de l'utilisation des interfaces est qu'il permet d'utiliser des objets de différents types de manière polymorphe. C'est parce que toute classe implémentant l'interface a au moins ces champs et fonctions.

Syntaxe

  • interface NomInterface {
  • parameterName: parameterType;
  • optionalParameterName ?: parameterType;
  • }

Remarques

Interfaces vs types d'alias

Les interfaces permettent de spécifier la forme d'un objet, par exemple pour un objet personne que vous pouvez spécifier

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

Cependant, que se passe-t-il si vous souhaitez, par exemple, représenter la manière dont une personne est stockée dans une base de données SQL? Étant donné que chaque entrée de base de données consiste en une ligne de forme [string, string, number] (donc un tableau de chaînes ou de nombres), vous ne pouvez pas représenter cela comme une forme d'objet, car la ligne n'a aucune propriété en tant que tel, c'est juste un tableau.

C'est une occasion où les types sont utiles. Au lieu de spécifier dans chaque fonction qui accepte une function processRow(row: [string, string, number]) paramètre de ligne function processRow(row: [string, string, number]) , vous pouvez créer un alias de type distinct pour une ligne, puis l'utiliser dans chaque fonction:

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

Documentation de l'interface officielle

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

Ajouter des fonctions ou des propriétés à une interface existante

Supposons que nous ayons une référence à la définition de type JQuery et que nous souhaitons l'étendre pour avoir des fonctions supplémentaires à partir d'un plug-in inclus et qui n'a pas de définition de type officielle. Nous pouvons facilement l'étendre en déclarant les fonctions ajoutées par le plug-in dans une déclaration d'interface distincte avec le même nom JQuery :

interface JQuery {
  pluginFunctionThatDoesNothing(): void;

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

Le compilateur fusionnera toutes les déclarations du même nom en un seul - voir la fusion des déclarations pour plus de détails.

Interface de classe

Déclarez public variables public et les méthodes de type dans l'interface pour définir comment un autre code dactylographié peut interagir avec lui.

interface ISampleClassInterface {
  sampleVariable: string;

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

Ici, nous créons une classe qui implémente l'interface.

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

L'exemple montre comment créer une interface ISampleClassInterface et une classe SampleClass qui implements l'interface.

Interface d'extension

Supposons que nous ayons une interface:

interface IPerson {
    name: string;
    age: number;

    breath(): void;
}

Et nous voulons créer une interface plus spécifique qui possède les mêmes propriétés que la personne, nous pouvons le faire en utilisant le mot extends clé extend:

interface IManager extends IPerson {
    managerId: number;

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

En outre, il est possible d'étendre plusieurs interfaces.

Utilisation d'interfaces pour appliquer des types

L'un des principaux avantages de Typescript est qu'il applique les types de données que vous transmettez à votre code pour éviter les erreurs.

Disons que vous faites une application de rencontre pour animaux de compagnie.

Vous avez cette fonction simple qui vérifie si deux animaux sont compatibles les uns avec les autres ...

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

C'est du code complètement fonctionnel, mais il serait trop facile pour quelqu'un, surtout pour d'autres personnes travaillant sur cette application qui n'ont pas écrit cette fonction, de ne pas savoir qu'ils sont censés lui transmettre des objets avec 'species' et 'age' Propriétés. Ils peuvent essayer par erreur checkCompatible(petOne.species, petTwo.species) , puis laisser les erreurs se produire lorsque la fonction essaie d'accéder à petOne.species.species ou petOne.species.age!

Une des manières d’empêcher que cela se produise est de spécifier les propriétés que nous voulons sur les paramètres de familier:

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

Dans ce cas, Typescript s'assurera que tout ce qui est passé à la fonction possède des propriétés 'species' et 'age' (si elles ont des propriétés supplémentaires), mais c'est une solution assez lourde, même avec seulement deux propriétés spécifiées. Avec les interfaces, il y a un meilleur moyen!

Tout d'abord, nous définissons notre interface:

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

Il ne nous reste plus qu'à spécifier le type de nos paramètres en tant que nouvelle interface, comme ça ...

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

... et Typescript s'assurera que les paramètres transmis à notre fonction contiennent les propriétés spécifiées dans l'interface Pet!

Interfaces Génériques

Comme les classes, les interfaces peuvent également recevoir des paramètres polymorphes (aka Generics).

Déclaration de paramètres génériques sur les interfaces

interface IStatus<U> {
    code: U;
}

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

Ici, vous pouvez voir que nos deux interfaces prennent des paramètres génériques, T et U.

Implémentation d'interfaces génériques

Nous allons créer une classe simple pour implémenter l'interface IEvents .

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

Créons des instances de notre classe State .

Dans notre exemple, la classe State gère un statut générique en utilisant IStatus<T> . De cette manière, l'interface IEvent<T> gérera également un 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));

Ici, notre classe d' State est typée 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);
});

Notre classe d' State est typée IStatus<Code> . De cette façon, nous pouvons transmettre un type plus complexe à notre méthode d’émission.

Comme vous pouvez le voir, les interfaces génériques peuvent être un outil très utile pour le code statique.

Utilisation d'interfaces pour le polymorphisme

La principale raison d'utiliser des interfaces pour obtenir un polymorphisme et fournir aux développeurs la possibilité d'implémenter à leur manière à l'avenir en implémentant les méthodes de l'interface.

Supposons que nous ayons une interface et trois classes:

interface Connector{
    doConnect(): boolean;
}

C'est l'interface du connecteur. Maintenant, nous allons implémenter cela pour la communication Wifi.

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
    }

}

Ici, nous avons développé notre classe concrète nommée WifiConnector qui a sa propre implémentation. Ceci est maintenant tapez Connector .

Nous créons maintenant notre System doté d'un composant Connector . Cela s'appelle l'injection de dépendance.

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

constructor(private connector: Connector) cette ligne est très importante ici. Connector est une interface et doit avoir doConnect() . Comme Connector est une interface, cette classe System a beaucoup plus de flexibilité. Nous pouvons transmettre n'importe quel type qui a implémenté une interface de Connector . Dans le futur, le développeur obtient plus de flexibilité. Par exemple, le développeur veut maintenant ajouter le module de connexion Bluetooth:

export class BluetoothConnector implements Connector{

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

}

Voir que Wifi et Bluetooth ont sa propre implémentation. Il existe différentes manières de se connecter. Cependant, les deux ont donc implémenté Type Connector le sont maintenant Type Connector . Pour que nous puissions transmettre n'importe lequel de ceux-ci à la classe System tant que paramètre constructeur. C'est ce qu'on appelle le polymorphisme. La classe System ne sait plus si c'est Bluetooth / Wifi, même si nous pouvons ajouter un autre module de communication comme Inferade, Bluetooth5 et tout simplement en implémentant l'interface de Connector .

Cela s'appelle Duck typing . Connector type de Connector est maintenant dynamique car doConnect() est juste un espace réservé et le développeur l'implémente comme le sien.

si au constructor(private connector: WifiConnector)WifiConnector est une classe concrète, que se passera-t-il? Ensuite System classe System ne couplera étroitement avec rien avec WifiConnector. Ici, l'interface a résolu notre problème par polymorphisme.

Implémentation implicite et forme d'objet

TypeScript supporte les interfaces, mais le compilateur génère du JavaScript, ce qui n'est pas le cas. Par conséquent, les interfaces sont effectivement perdues lors de l'étape de compilation. C'est pourquoi la vérification de type sur les interfaces repose sur la forme de l'objet - c'est-à-dire si l'objet prend en charge les champs et les fonctions de l'interface - et non sur le fait que l'interface soit réellement implémentée ou non.

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

Ainsi, même si Ball IKickable pas explicitement IKickable , une instance de Ball peut être affectée à (et manipulée comme) un IKickable , même si le type est spécifié.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow