Объединение объявлений
Введение
Некоторые уникальные концепции в TypeScript описывают форму JavaScript-объектов на уровне типов. Одним из особенно уникальных для TypeScript примеров является концепция «объединения объявлений». Понимание этой концепции даст вам преимущество при работе с существующим JavaScript. Она также открывает двери к более продвинутым концепциям абстракции.
В рамках этой статьи «объединение объявлений» означает, что компилятор объединяет два отдельных объявления с одинаковым именем в одно определение. Это объединённое определение имеет характеристики обоих исходных объявлений. Любое количество объявлений может быть объединено; оно не ограничивается только двумя объявлениями.
Основные концепции
В TypeScript объявление создаёт сущности, по меньшей мере, в трёх группах: пространство имён, тип или значение. Объявления, создающие пространство имён, создают пространство имён, которое содержит имена, к которым обращаются с использованием точечной нотации. Объявления, создающие типы, делают именно это: они создают тип, который виден с заданной формой и привязан к данному имени. Наконец, объявления, создающие значения, создают значения, которые видны в выходном JavaScript.
| Тип объявления | Пространство имён | Тип | Значение |
|---|---|---|---|
| Пространство имён | X | X | |
| Класс | X | X | |
| Перечисление | X | X | |
| Интерфейс | X | ||
| Псевдоним типа | X | ||
| Функция | X | ||
| Переменная | X |
Понимание того, что создаётся с помощью каждого объявления, поможет вам понять, что объединяется при выполнении объединения объявлений.
Объединение интерфейсов
Самый простой и, возможно, наиболее распространённый тип объединения объявлений — объединение интерфейсов. На базовом уровне объединение механически объединяет члены обоих объявлений в один интерфейс с тем же именем.
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = { height: 5, width: 6, scale: 10 }; Нефункциональные члены интерфейсов должны быть уникальными. Если они не уникальны, они должны быть одного типа. Компилятор выдаст ошибку, если оба интерфейса объявят не-функциональный член с одинаковым именем, но разных типов.
Для функциональных членов каждый функциональный член с одинаковым именем обрабатывается как описание перегрузки одной и той же функции. Также следует отметить, что в случае объединения интерфейса A с последующим интерфейсом A, второй интерфейс будет иметь более высокий приоритет, чем первый.
То есть, в примере:
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
} Три интерфейса объединятся в одно объявление следующим образом:
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
} Обратите внимание, что элементы каждой группы сохраняют тот же порядок, но сами группы объединяются, и последующие наборы перегрузок упорядочиваются первыми.
Исключение из этого правила — специализированные подписи. Если подпись имеет параметр, тип которого является единственным строковым литеральным типом (например, не объединением строковых литералов), то он будет «поднят» вверх в своём объединённом списке перегрузок.
Например, следующие интерфейсы объединятся:
interface Document {
createElement(tagName: any): Element;
}
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
} Результирующее объединённое объявление Document будет следующим:
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
} Объединение пространств имён
Аналогично интерфейсам, пространства имён с одинаковым именем также объединят свои члены. Поскольку пространства имён создают как пространство имён, так и значение, нам нужно понять, как объединяются оба.
Для объединения пространств имён определения типов из экспортированных интерфейсов, объявленных в каждом пространстве имён, объединяются, образуя одно пространство имён с объединёнными определениями интерфейсов внутри.
Для объединения значения пространства имён, в каждой точке объявления, если пространство имён уже существует с данным именем, оно дополнительно расширяется, взяв существующее пространство имён и добавив экспортированные члены второго пространства имён в первое.
Объединение объявления Animals в этом примере:
namespace Animals {
export class Zebra {}
}
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Dog {}
} эквивалентно:
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Zebra {}
export class Dog {}
} Эта модель объединения пространств имён является полезной отправной точкой, но нам также нужно понять, что происходит с неэкспортированными членами. Неэкспортированные члены видны только в исходном (не объединённом) пространстве имён. Это означает, что после объединения объединённые члены, которые пришли из других объявлений, не могут видеть неэкспортированные члены.
Это можно увидеть более чётко в этом примере:
namespace Animal {
let haveMuscles = true;
export function animalsHaveMuscles() {
return haveMuscles;
}
}
namespace Animal {
export function doAnimalsHaveMuscles() {
return haveMuscles; // Error, because haveMuscles is not accessible here
}
} Так как haveMuscles не экспортирован, только функция animalsHaveMuscles, которая разделяет то же самое не объединённое пространство имён, может видеть символ. Функция doAnimalsHaveMuscles, даже если она является частью объединённого пространства имён Animal, не может видеть этот неэкспортированный член.
Объединение пространств имён с классами, функциями и перечислениями
Пространства имён достаточно гибкие, чтобы объединяться и с другими типами объявлений. Для этого объявление пространства имён должно следовать за объявлением, с которым оно будет объединяться. Результирующее объявление имеет свойства обоих типов объявлений. TypeScript использует эту возможность для моделирования некоторых шаблонов в JavaScript, а также в других языках программирования.
Объединение пространств имён с классами
Это даёт пользователю способ описания внутренних классов.
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {}
} Правила видимости объединённых членов такие же, как описано в разделе Объединение пространств имён, поэтому мы должны экспортировать класс AlbumLabel для того, чтобы объединённый класс его видел. В итоге получается класс, управляемый внутри другого класса. Вы также можете использовать пространства имён для добавления больше статических членов в существующий класс.
В дополнение к шаблону внутренних классов, вы также можете быть знакомы с практикой JavaScript по созданию функции, а затем её дальнейшему расширению путём добавления свойств к функции. TypeScript использует объединение объявлений для построения таких определений безопасным для типов способом.
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
export let suffix = "";
export let prefix = "Hello, ";
}
console.log(buildLabel("Sam Smith")); Аналогично, пространства имён могут использоваться для расширения перечислений статическими членами:
enum Color {
red = 1,
green = 2,
blue = 4,
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") {
return Color.red + Color.blue;
} else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
} Запрещённые объединения
Не все объединения разрешены в TypeScript. В настоящее время классы не могут объединяться с другими классами или с переменными. Для получения информации о моделировании объединения классов см. раздел Mixin в TypeScript.
Модульное расширение
Хотя JavaScript-модули не поддерживают объединение, вы можете подправить существующие объекты, импортировав их и затем обновив. Давайте рассмотрим пример Observable:
// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// map.ts
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
// ... another exercise for the reader
}; Это работает и в TypeScript, но компилятор не знает о Observable.prototype.map. Вы можете использовать модульное расширение, чтобы сообщить компилятору об этом:
// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (f) {
// ... another exercise for the reader
};
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed()); Имя модуля разрешается так же, как спецификаторы модулей в import/export. Дополнительную информацию см. в разделе Модули. Затем объявления в расширении объединяются так, как если бы они были объявлены в том же файле, что и исходные.
Однако есть два ограничения, которые следует учитывать:
- Вы не можете объявлять новые объявления верхнего уровня в расширении — только исправления существующих объявлений.
- Экспорт по умолчанию также не может быть расширен, только именованные экспорты (поскольку вам нужно расширить экспорт по его экспортированному имени, и
default— зарезервированное слово — см. #14080 для получения подробностей)
Глобальное расширение
Вы также можете добавлять объявления в глобальную область видимости изнутри модуля:
// observable.ts
export class Observable<T> {
// ... still no implementation ...
}
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Array.prototype.toObservable = function () {
// ...
}; Глобальные расширения имеют такое же поведение и ограничения, как и модульные расширения.
© 2012-2023 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/declaration-merging.html