Поиск…


Вступление

Интерфейсы определяют список полей и функций, которые можно ожидать в любом классе, реализующем интерфейс. И наоборот, класс не может реализовать интерфейс, если он не имеет каждого поля и функции, указанных на интерфейсе.

Основное преимущество использования интерфейсов состоит в том, что он позволяет использовать объекты разных типов полиморфным способом. Это связано с тем, что любой класс, реализующий интерфейс, имеет по крайней мере те поля и функции.

Синтаксис

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

замечания

Интерфейсы и псевдонимы типов

Интерфейсы хороши для указания формы объекта, например, для объекта человека, который вы могли бы указать

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

Однако что, если вы хотите, например, представить, как человек хранится в базе данных SQL? Поскольку каждая запись БД состоит из строки формы [string, string, number] (так что массив строк или чисел), вы не можете представить это как форму объекта, потому что строка не имеет никаких свойств как таковой, это всего лишь массив.

Это случай, когда типы полезны. Вместо указания в каждой функции, которая принимает функцию параметра function processRow(row: [string, string, number]) , вы можете создать отдельный псевдоним типа для строки, а затем использовать это в каждой функции:

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

Документация официального интерфейса

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

Добавление функций или свойств в существующий интерфейс

Предположим, что у нас есть ссылка на определение типа JQuery и мы хотим расширить его, чтобы иметь дополнительные функции из плагина, который мы включили, и который не имеет официального определения типа. Мы можем легко расширить его, объявив функции, добавленные плагином, в отдельное объявление интерфейса с тем же именем JQuery :

interface JQuery {
  pluginFunctionThatDoesNothing(): void;

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

Компилятор объединит все объявления с одним и тем же именем в один - см. Объявление слияния для более подробной информации.

Интерфейс класса

Объявите public переменные и методы в интерфейсе, чтобы определить, как с ним может взаимодействовать другой код машинописного текста.

interface ISampleClassInterface {
  sampleVariable: string;

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

Здесь мы создаем класс, реализующий интерфейс.

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

В этом примере показано, как создать интерфейс ISampleClassInterface и класс SampleClass который implements интерфейс.

Расширение интерфейса

Предположим, что у нас есть интерфейс:

interface IPerson {
    name: string;
    age: number;

    breath(): void;
}

И мы хотим создать более конкретный интерфейс, который обладает теми же свойствами человека, мы можем сделать это, используя ключевое слово extends :

interface IManager extends IPerson {
    managerId: number;

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

Кроме того, возможно расширить несколько интерфейсов.

Использование интерфейсов для принудительного использования типов

Одним из основных преимуществ TypScript является то, что он применяет типы данных, которые вы передаете вокруг своего кода, чтобы предотвратить ошибки.

Предположим, вы делаете приложение для знакомств с домашними животными.

У вас есть эта простая функция, которая проверяет совместимость двух домашних животных друг с другом ...

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

Это полностью функциональный код, но для кого-то, особенно других людей, работающих над этим приложением, которые не пишут эту функцию, было бы слишком легко, чтобы они не знали, что они должны передавать ему объекты с «видами» и «возрастом», свойства. Они могут ошибочно попробовать checkCompatible(petOne.species, petTwo.species) а затем оставлять для выяснения ошибок, возникающих при попытке выполнить функцию petOne.species.species или petOne.species.age!

Один из способов предотвратить это - указать свойства, которые мы хотим по параметрам домашних животных:

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

В этом случае TypScript будет убедиться, что все, переданное функции, имеет свойства «вид» и «возраст» (это нормально, если у них есть дополнительные свойства), но это немного громоздкое решение, даже если задано только два свойства. С интерфейсами есть лучший способ!

Сначала мы определяем наш интерфейс:

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

Теперь все, что нам нужно сделать, это указать тип наших параметров как наш новый интерфейс, например ...

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

... и TypScript будут следить за тем, чтобы параметры, переданные нашей функции, содержали свойства, указанные в интерфейсе Pet!

Общие интерфейсы

Подобно классам, интерфейсы могут также получать полиморфные параметры (также как Generics).

Объявление общих параметров на интерфейсах

interface IStatus<U> {
    code: U;
}

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

Здесь вы можете видеть, что наши два интерфейса принимают некоторые общие параметры, T и U.

Внедрение общих интерфейсов

Мы создадим простой класс для реализации интерфейса 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;
    }
    
}

Давайте создадим некоторые примеры нашего государственного класса.

В нашем примере класс State будет обрабатывать общий статус, используя IStatus<T> . Таким образом, интерфейс IEvent<T> также будет обрабатывать 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));

Здесь наше State класс набирается , как 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);
});

Наш класс State напечатан как IStatus<Code> . Таким образом, мы можем передать более сложный тип нашему методу emit.

Как вы можете видеть, общие интерфейсы могут быть очень полезным инструментом для статически типизированного кода.

Использование интерфейсов для полиморфизма

Основная причина использования интерфейсов для достижения полиморфизма и предоставления разработчикам возможности реализовать на собственном пути в будущем, реализуя методы интерфейса.

Предположим, что у нас есть интерфейс и три класса:

interface Connector{
    doConnect(): boolean;
}

Это интерфейс разъема. Теперь мы будем реализовывать это для общения с Wi-Fi.

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
    }

}

Здесь мы разработали наш конкретный класс WifiConnector который имеет свою собственную реализацию. Теперь это тип Connector .

Теперь мы создаем нашу System с компонентом Connector . Это называется инъекцией зависимостей.

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

constructor(private connector: Connector) эта линия очень важна здесь. Connector - это интерфейс и должен иметь doConnect() . Поскольку Connector является интерфейсом, этот класс System обладает гораздо большей гибкостью. Мы можем передать любой тип, который реализовал интерфейс Connector . В будущем разработчик проявит большую гибкость. Например, теперь разработчик хочет добавить модуль Bluetooth Connection:

export class BluetoothConnector implements Connector{

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

}

Смотрите, что Wi-Fi и Bluetooth имеют свою собственную реализацию. Там есть другой способ подключения. Однако, следовательно, оба реализовали Type Connector , теперь они являются Type Connector . Чтобы мы могли передать любой из них классу System в качестве параметра конструктора. Это называется полиморфизмом. Класс System теперь не знают , является ли это Bluetooth / Wi - Fi , даже мы можем добавить еще один модуль связи , как Inferade, Bluetooth5 и вообще, просто реализует Connector интерфейс.

Это называется утиная печать . Тип Connector теперь динамический, поскольку doConnect() - это просто местозаполнитель, и разработчик реализует это как свою собственную.

если у constructor(private connector: WifiConnector) где WifiConnector - это конкретный класс, что произойдет? Тогда System класс будет тесно связан только с WifiConnector. Здесь интерфейс решал нашу проблему полиморфизмом.

Неявная реализация и форма объекта

TypeScript поддерживает интерфейсы, но компилятор выводит JavaScript, а это не так. Поэтому интерфейсы фактически теряются на этапе компиляции. Вот почему проверка типов на интерфейсах зависит от формы объекта, а именно от того, поддерживает ли объект поля и функции на интерфейсе, а не от того, действительно ли реализован интерфейс или нет.

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

Таким образом, даже если Ball не реализует IKickable явно, экземпляр Ball может быть назначен (и управляться как) IKickable , даже если этот тип указан.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow