Объединение интерфейсов в 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: TypeScript