Объединение интерфейсов в TypeScript

В одном из наших предыдущих руководств по TypeScript мы кратко говорили о слиянии объявлений (также объединение деклараций). В этом мануале мы углубимся в эту тему и разберем интерфейсы, а также слияние объявлений интерфейсов.

Читайте также: Разбираем миксины в TypeScript

Что такое объединение деклараций?

Объединение деклараций – это процесс, при котором компилятор TypeScript объединяет два или более типа в одно объявление при условии, что они имеют одинаковое имя.

TypeScript допускает слияние разных типов: интерфейс с интерфейсом, enum с enum, пространство имен с пространством имен и т.д. Примечательно, что при этом TypeScript не поддерживает слияние классов с классами. Если вам нужна такая функциональность, используйте миксины.

Давайте начнем наше знакомство с объединением интерфейсов с одного примера:

interface Person {
  name: string;
}

interface Person {
  age: number;
}

interface Person {
  height: number;
}

class Employee implements Person {
  name = "Mensah"
  age = 100;
  height = 40
}

const employee = new Employee();
console.log(employee) // {name: "Mensah", age: 100, height: 40}

Поскольку все интерфейсы были объявлены с одним и тем же именем – Person – они объединились в одно объявление. Поэтому класс Employee содержит свойства из всех интерфейсов.

Свойства с одинаковыми именами

Если какие-либо из объединяемых интерфейсов содержат свойства с одинаковыми именами (и эти свойства не являются функцией), тогда type свойств должен быть тоже одинаковым, иначе компилятор выдаст ошибку.

interface Person {
  name: string;
  zipCode: string;
}

interface Person {
  age: number;
  zipCode: string; // acceptable
}

interface Person {
   zipCode: number; // error
}

Свойства-функции с одинаковыми именами

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

interface Person {
  speak(words: string);
}

interface Person {
  speak(words: number);
}

const person: Person = {
  speak: (wordsOrNum) => wordsOrNum
}

console.log(person.speak("Hi")) // speak(words: string) is used
console.log(person.speak(2)) // speak(words: number) is used

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

interface Person {
  speak(words:string);
}

interface Person {
  speak(words: any);
}

interface Person {
  speak(words: number);
  speak(words: boolean);
}

// This is how the final merged interface looks like
interface Person {
  // functions in the last interface appear at the top
  speak(words: number);
  speak(words: boolean);

  // function in the middle interface appears next
  speak(words: any):number;

  // function in the first interface appears last
  speak(words: string):string;
}

Причина такого поведения – в том, что объявленные позже интерфейсы имеют более высокий приоритет по сравнению с интерфейсами, объявленными ранее. Итак, в приведенном выше примере speak(words: string) никогда не будет вызываться, потому что в финальном интерфейсе Person интерфейс speak(words: any):number предшествует интерфейсу speak(words: string):string. А поскольку any может обозначать любой тип, он вызывается, даже если в качестве аргумента передается string.

Чтобы доказать это, рассмотрим эту строку. При наведении указателя мыши на переменную per в приведенном ниже коде будет отображаться const per: number, а не const per: string, даже если мы передаем аргумент string.

const per = person.speak("bacon");

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

interface Person {
  speak(words: number);
  speak(words: "World!"); // string literal type
}

interface Person {
  speak(words: "Hello"); // string literal type
}

interface Person {
  speak(words: string);
}


// merged interface output.
interface Person {
  // string literals are given precedence
  speak(words: "Hello");
  speak(words: "World!");

  // the rest of the functions are arranged using the same rules.
  speak(words: string);
  speak(words: number);
}

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

Tags:

Добавить комментарий