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