Ricerca…


introduzione

Un'interfaccia specifica un elenco di campi e funzioni che possono essere previsti su qualsiasi classe che implementa l'interfaccia. Al contrario, una classe non può implementare un'interfaccia a meno che non abbia tutti i campi e le funzioni specificati nell'interfaccia.

Il vantaggio principale dell'uso delle interfacce è che consente di utilizzare oggetti di tipi diversi in modo polimorfico. Questo perché ogni classe che implementa l'interfaccia ha almeno quei campi e funzioni.

Sintassi

  • interfaccia InterfaceName {
  • parameterName: parameterType;
  • optionalParameterName ?: parameterType;
  • }

Osservazioni

Interfacce vs tipo alias

Le interfacce sono utili per specificare la forma di un oggetto, ad es. Per un oggetto persona che potresti specificare

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

Tuttavia, cosa succede se si desidera rappresentare, ad esempio, il modo in cui una persona è memorizzata in un database SQL? Visto che ogni voce di DB è composta da una riga di forma [string, string, number] (quindi una serie di stringhe o numeri), non è possibile rappresentarlo come una forma di oggetto, perché la riga non ha alcuna proprietà come tale, è solo un array.

Questa è un'occasione in cui i tipi diventano utili. Invece di specificare in ogni funzione che accetta una function processRow(row: [string, string, number]) parametro row function processRow(row: [string, string, number]) , è possibile creare un alias di tipo separato per una riga e quindi utilizzarlo in ogni funzione:

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

Documentazione ufficiale dell'interfaccia

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

Aggiungi funzioni o proprietà a un'interfaccia esistente

Supponiamo di avere un riferimento alla definizione del tipo JQuery e vogliamo estenderlo per avere funzioni aggiuntive da un plugin che abbiamo incluso e che non ha una definizione di tipo ufficiale. Possiamo facilmente estenderlo dichiarando le funzioni aggiunte dal plugin in una dichiarazione di interfaccia separata con lo stesso nome JQuery :

interface JQuery {
  pluginFunctionThatDoesNothing(): void;

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

Il compilatore unirà tutte le dichiarazioni con lo stesso nome in un'unica: vedere la fusione delle dichiarazioni per ulteriori dettagli.

Interfaccia di classe

Dichiarare variabili public e metodi digitare nell'interfaccia per definire in che modo un altro codice dattiloscritto può interagire con esso.

interface ISampleClassInterface {
  sampleVariable: string;

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

Qui creiamo una classe che implementa l'interfaccia.

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'esempio mostra come creare un'interfaccia ISampleClassInterface e una classe SampleClass che implements l'interfaccia.

Estensione dell'interfaccia

Supponiamo di avere un'interfaccia:

interface IPerson {
    name: string;
    age: number;

    breath(): void;
}

E vogliamo creare un'interfaccia più specifica che abbia le stesse proprietà della persona, possiamo farlo usando la parola chiave extends :

interface IManager extends IPerson {
    managerId: number;

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

Inoltre è possibile estendere più interfacce.

Utilizzo delle interfacce per imporre tipi

Uno dei principali vantaggi di Typescript è che applica i tipi di valori di dati che si stanno passando attorno al codice per evitare errori.

Diciamo che stai facendo una domanda di appuntamenti per animali domestici.

Hai questa semplice funzione che controlla se due animali domestici sono compatibili tra loro ...

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

Questo è un codice completamente funzionale, ma sarebbe troppo facile per qualcuno, specialmente per le altre persone che lavorano su questa applicazione che non hanno scritto questa funzione, di ignorare che dovrebbero passare oggetti con "specie" ed "età" proprietà. Potrebbero erroneamente provare checkCompatible(petOne.species, petTwo.species) e quindi essere lasciati a capire gli errori generati quando la funzione tenta di accedere a petOne.species.species o petOne.species.age!

Un modo in cui possiamo evitare che questo accada è specificare le proprietà che vogliamo sui parametri dell'animale domestico:

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

In questo caso, Typescript si assicurerà che tutto ciò che è passato alla funzione abbia proprietà 'species' e 'age' (è ok se hanno proprietà aggiuntive), ma questa è una soluzione un po 'ingombrante, anche con solo due proprietà specificate. Con le interfacce, c'è un modo migliore!

Per prima cosa definiamo la nostra interfaccia:

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

Ora tutto ciò che dobbiamo fare è specificare il tipo dei nostri parametri come la nostra nuova interfaccia, in questo modo ...

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

... e Typescript farà in modo che i parametri passati alla nostra funzione contengano le proprietà specificate nell'interfaccia dell'animale domestico!

Interfacce generiche

Come le classi, anche le interfacce possono ricevere parametri polimorfici (anche generici).

Dichiarazione dei parametri generici sulle interfacce

interface IStatus<U> {
    code: U;
}

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

Qui, puoi vedere che le nostre due interfacce prendono alcuni parametri generici, T e U.

Implementazione di interfacce generiche

Creeremo una classe semplice per implementare l'interfaccia 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;
    }
    
}

Creiamo alcuni esempi della nostra classe di stato .

Nel nostro esempio, la classe State gestirà uno stato generico usando IStatus<T> . In questo modo, l'interfaccia IEvent<T> gestirà anche 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));

Qui la nostra classe di State viene digitata come 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);
});

La nostra classe State è stata digitata come IStatus<Code> . In questo modo, siamo in grado di passare un tipo più complesso al nostro metodo emit.

Come puoi vedere, le interfacce generiche possono essere uno strumento molto utile per il codice tipizzato staticamente.

Utilizzo di interfacce per polimorfismo

La ragione principale per utilizzare le interfacce per ottenere il polimorfismo e fornire agli sviluppatori di implementare a loro modo in futuro implementando i metodi dell'interfaccia.

Supponiamo di avere un'interfaccia e tre classi:

interface Connector{
    doConnect(): boolean;
}

Questa è l'interfaccia del connettore. Ora implementeremo quello per la comunicazione 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
    }

}

Qui abbiamo sviluppato la nostra classe concreta denominata WifiConnector che ha una sua implementazione. Questo è ora il tipo Connector .

Ora stiamo creando il nostro System che ha un Connector componente. Questo è chiamato iniezione di dipendenza.

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

constructor(private connector: Connector) questa linea è molto importante qui. Connector è un'interfaccia e deve avere doConnect() . Poiché Connector è un'interfaccia, questo System classe ha molta più flessibilità. Possiamo passare qualsiasi tipo che ha implementato l'interfaccia Connector . Nel futuro sviluppatore ottiene più flessibilità. Ad esempio, ora lo sviluppatore vuole aggiungere il modulo di connessione Bluetooth:

export class BluetoothConnector implements Connector{

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

}

Vedi che Wifi e Bluetooth hanno una propria implementazione. Esiste un modo diverso per connettersi. Tuttavia, entrambi hanno implementato il Connector tipo, ora sono di tipo Connector . In modo che possiamo passare nessuno di questi alla classe System come parametro del costruttore. Questo è chiamato polimorfismo. Il System classe non è al corrente se si tratta di Bluetooth / Wifi, anche se possiamo aggiungere un altro modulo di comunicazione come Inferade, Bluetooth5 e qualsiasi altra cosa, semplicemente implementando l'interfaccia Connector .

Questo è chiamato Duck typing . Connector tipo di Connector è ora dinamico poiché doConnect() è solo un segnaposto e lo sviluppatore lo implementa come suo.

se al constructor(private connector: WifiConnector) dove WifiConnector è una classe concreta cosa succederà? Quindi la classe di System si accoppierà strettamente solo con WifiConnector, nient'altro. Qui l'interfaccia ha risolto il nostro problema con il polimorfismo.

Implementazione implicita e forma dell'oggetto

TypeScript supporta le interfacce, ma il compilatore restituisce JavaScript, che non lo fa. Pertanto, le interfacce vengono effettivamente perse nel passaggio di compilazione. Questo è il motivo per cui il controllo di tipo sulle interfacce si basa sulla forma dell'oggetto - ovvero se l'oggetto supporta i campi e le funzioni sull'interfaccia - e non sul fatto che l'interfaccia sia effettivamente implementata o meno.

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

Quindi, anche se Ball non implementa esplicitamente IKickable , un'istanza Ball può essere assegnata a (e manipolata come) un IKickable , anche quando il tipo è specificato.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow