Spec-Zone.ru › TypeScript 5.1

Глубокий анализ

Теория файлов деклараций: Глубокий анализ

Структурирование модулей для придания желаемой формы API может быть сложным. Например, нам может потребоваться модуль, который можно вызывать с или без new для получения различных типов, имеющий различные именованные типы, упорядоченные в иерархии, а также некоторые свойства объекта модуля.

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

Основные понятия

Вы сможете полностью понять, как создать любой вид декларации, поняв некоторые основные понятия работы TypeScript.

Типы

Если вы читаете это руководство, вы, вероятно, уже примерно знаете, что такое тип в TypeScript. Более конкретно, тип вводится с помощью:

  • Декларации псевдонима типа (type sn = number | string;)
  • Декларации интерфейса (interface I { x: number[]; })
  • Декларации класса (class C { })
  • Декларации перечисления (enum E { A, B, C })
  • Декларации import, которая ссылается на тип

Каждая из этих форм декларации создает новое имя типа.

Значения

Как и с типами, вы, вероятно, уже понимаете, что такое значение. Значения — это имена во время выполнения, на которые мы можем ссылаться в выражениях. Например, let x = 5; создает значение, называемое x.

Более конкретно, следующие вещи создают значения:

  • Декларации let, const, и var
  • Декларация namespace или module, которая содержит значение
  • Декларация enum
  • Декларация class
  • Декларация import, которая ссылается на значение
  • Декларация function

Пространства имен

Типы могут существовать в пространствах имен. Например, если у нас есть декларация let x: A.B.C, мы говорим, что тип C происходит из пространства имен A.B.

Это тонкое, но важное различие — здесь A.B не обязательно является типом или значением.

Простые комбинации: Одно имя, несколько значений

Учитывая имя A, мы можем найти до трёх различных значений для A: тип, значение или пространство имен. Как интерпретируется имя, зависит от контекста его использования. Например, в декларации let m: A.A = A; имя A используется сначала как пространство имен, затем как имя типа, а затем как значение. Эти значения могут ссылаться на совершенно разные декларации!

Это может показаться запутанным, но на самом деле это очень удобно, пока мы не перегружаем вещи чрезмерно. Давайте рассмотрим некоторые полезные аспекты этого объединяющего поведения.

Встроенные комбинации

Внимательные читатели заметят, что, например, class появилось как в списке типов, так и в списке значений. Декларация class C { } создает две вещи: тип C, который ссылается на форму экземпляра класса, и значение C, которое ссылается на функцию-конструктор класса. Декларации перечислений ведут себя аналогично.

Пользовательские комбинации

Предположим, мы написали файл модуля foo.d.ts:

export var SomeVar: { a: SomeType };
export interface SomeType {
  count: number;
}

Затем использовали его:

import * as foo from "./foo";
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);

Это работает достаточно хорошо, но мы можем представить себе, что SomeType и SomeVar очень тесно связаны, так что вы хотите, чтобы у них было одинаковое имя. Мы можем использовать объединение, чтобы представить эти два разных объекта (значение и тип) под одним именем Bar:

export var Bar: { a: Bar };
export interface Bar {
  count: number;
}

Это предоставляет отличную возможность для деструктуризации в коде, использующем модуль:

import { Bar } from "./foo";
let x: Bar = Bar.a;
console.log(x.count);

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

Расширенные комбинации

Некоторые виды деклараций могут быть объединены в нескольких декларациях. Например, class C { } и interface C { } могут сосуществовать и оба вносить свойства в типы C.

Это законно, пока не создаётся конфликт. Общее правило — значения всегда конфликтуют с другими значениями с тем же именем, если они не объявлены как namespaceы; типы будут конфликтовать, если они объявлены с помощью декларации псевдонима типа (type s = string); и пространства имен никогда не конфликтуют.

Давайте посмотрим, как это можно использовать.

Добавление с помощью интерфейса

Мы можем добавить дополнительные члены к интерфейсу с помощью другой декларации интерфейса:

interface Foo {
  x: number;
}
// ... elsewhere ...
interface Foo {
  y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

Это также работает с классами:

class Foo {
  x: number;
}
// ... elsewhere ...
interface Foo {
  y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK

Обратите внимание, что мы не можем добавлять к псевдонимам типов (type s = string;). используя интерфейс.

Добавление с помощью пространства имен

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

Например, мы можем добавить статический член к классу:

class C {}
// ... elsewhere ...
namespace C {
  export let x: number;
}
let y = C.x; // OK

Обратите внимание, что в этом примере мы добавили значение к статической части C (его функции-конструктору). Это потому, что мы добавили значение, а контейнер для всех значений — это другое значение (типы содержатся в пространствах имен, а пространства имен содержатся в других пространствах имен).

Мы также могли бы добавить типизированное пространство имен к классу:

class C {}
// ... elsewhere ...
namespace C {
  export interface D {}
}
let y: C.D; // OK

В этом примере пространство имен C не существовало, пока мы не написали декларацию namespace для него. Значение C как пространства имен не конфликтует со значениями и типами C, созданными классом.

Наконец, мы можем выполнить множество различных слияний с помощью деклараций namespace. Это не очень реалистичный пример, но показывает различные интересные поведения:

namespace X {
  export interface Y {}
  export class Z {}
}

// ... elsewhere ...
namespace X {
  export var Y: number;
  export namespace Z {
    export class C {}
  }
}
type X = string;

В этом примере первый блок создает следующие значения имен:

  • Значение X (поскольку декларация namespace содержит значение Z)
  • Пространство имен X (поскольку декларация namespace содержит тип Y)
  • Тип Y в пространстве имен X
  • Тип Z в пространстве имен X (форма экземпляра класса)
  • Значение Z, которое является свойством значения X (функции-конструктора класса)

Второй блок создает следующие значения имен:

  • Значение Y (типа number), которое является свойством значения X
  • Пространство имен Z
  • Значение Z, которое является свойством значения X
  • Тип C в пространстве имен X.Z
  • Значение C, которое является свойством значения X.Z
  • Тип X

© 2012-2024 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/declaration-files/deep-dive.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API