Spec-Zone.ru › TypeScript 5.1

Модули

Начиная с ECMAScript 2015, JavaScript имеет концепцию модулей. TypeScript разделяет эту концепцию.

Модули выполняются в собственном пространстве имен, а не в глобальном; это означает, что переменные, функции, классы и т.д., объявленные в модуле, не видны за пределами модуля, если они не экспортированы явно с помощью одного из export форм. И наоборот, для использования переменной, функции, класса, интерфейса и т.д., экспортированных из другого модуля, необходимо импортировать их с помощью одной из import форм.

Модули являются декларативными; отношения между модулями задаются в терминах импорта и экспорта на уровне файла.

Модули импортируют друг друга с помощью загрузчика модулей. Во время выполнения загрузчик модулей отвечает за поиск и выполнение всех зависимостей модуля перед его выполнением. Известными загрузчиками модулей, используемыми в JavaScript, являются загрузчик Node.js для CommonJS модулей и загрузчик RequireJS для AMD модулей в веб-приложениях.

В TypeScript, так же как и в ECMAScript 2015, любой файл, содержащий верхнеуровневый import или export считается модулем. И наоборот, файл без каких-либо верхнеуровневых import или export объявлений обрабатывается как скрипт, содержимое которого доступно в глобальном пространстве имен (и, следовательно, для модулей тоже).

Экспорт

Экспорт объявления

Любое объявление (например, переменная, функция, класс, псевдоним типа или интерфейс) может быть экспортировано, добавив ключевое слово export.

StringValidator.ts
export interface StringValidator {
  isAcceptable(s: string): boolean;
}
ZipCodeValidator.ts
import { StringValidator } from "./StringValidator";

export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}

Выражения экспорта

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

class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };

Переэкспорт

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

ParseIntBasedZipCodeValidator.ts
export class ParseIntBasedZipCodeValidator {
  isAcceptable(s: string) {
    return s.length === 5 && parseInt(s).toString() === s;
  }
}

// Export original validator but rename it
export { ZipCodeValidator as RegExpBasedZipCodeValidator } from "./ZipCodeValidator";

Необязательно, модуль может обернуть один или несколько модулей и объединить все их экспорты с помощью синтаксиса export * from "module".

AllValidators.ts
export * from "./StringValidator"; // exports 'StringValidator' interface
export * from "./ZipCodeValidator"; // exports 'ZipCodeValidator' class and 'numberRegexp' constant value
export * from "./ParseIntBasedZipCodeValidator"; //  exports the 'ParseIntBasedZipCodeValidator' class
// and re-exports 'RegExpBasedZipCodeValidator' as alias
// of the 'ZipCodeValidator' class from 'ZipCodeValidator.ts'
// module.

Импорт

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

Импорт одного экспорта из модуля

import { ZipCodeValidator } from "./ZipCodeValidator";

let myValidator = new ZipCodeValidator();

Импорты также могут быть переименованы

import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();

Импорт всего модуля в одну переменную и использование ее для доступа к экспортам модуля

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

Импорт модуля только для побочных эффектов

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

import "./my-module.js";

Импорт типов

До TypeScript 3.8 вы можете импортировать тип, используя import. С TypeScript 3.8 вы можете импортировать тип, используя выражение import или import type.

// Re-using the same import
import { APIResponseType } from "./api";

// Explicitly use import type
import type { APIResponseType } from "./api";

// Explicitly pull out a value (getResponse) and a type (APIResponseType) 
import { getResponse, type APIResponseType} from "./api";

Любой явно помеченный type импорт гарантированно будет удален из вашего JavaScript, а такие инструменты, как Babel, смогут сделать лучшие предположения о вашем коде с помощью флага компилятора isolatedModules. Дополнительную информацию можно найти в примечаниях к выпуску 3.8.

С TypeScript 4.5 вы можете использовать модификатор type для отдельных именованных импортов.

import { someFunc, type BaseType } from "./some-module.js";

Экспорт по умолчанию

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

default экспорты очень удобны. Например, библиотека, подобная jQuery, может иметь экспорт по умолчанию jQuery или $, который мы, вероятно, также импортируем под именем $ или jQuery.

JQuery.d.ts
declare let $: JQuery;
export default $;
App.ts
import $ from "jquery";

$("button.continue").html("Next Step...");

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

ZipCodeValidator.ts
export default class ZipCodeValidator {
  static numberRegexp = /^[0-9]+$/;
  isAcceptable(s: string) {
    return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
  }
}
Test.ts
import validator from "./ZipCodeValidator";

let myValidator = new validator();

или

StaticZipCodeValidator.ts
const numberRegexp = /^[0-9]+$/;

export default function (s: string) {
  return s.length === 5 && numberRegexp.test(s);
}
Test.ts
import validate from "./StaticZipCodeValidator";

let strings = ["Hello", "98052", "101"];

// Use function validate
strings.forEach((s) => {
  console.log(`"${s}" ${validate(s) ? "matches" : "does not match"}`);
});

default экспорты также могут быть просто значениями:

OneTwoThree.ts
export default "123";
Log.ts
import num from "./OneTwoThree";

console.log(num); // "123"

Экспорт всех как x

С TypeScript 3.8 вы можете использовать export * as ns в качестве сокращения для переэкспорта другого модуля с именем:

export * as utilities from "./utilities";

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

import { utilities } from "./index";

export = и import = require()

Как CommonJS, так и AMD обычно имеют понятие объекта exports , который содержит все экспорты из модуля.

Они также поддерживают замену объекта exports на настраиваемый единственный объект. Экспорты по умолчанию предназначены для замены этого поведения; однако, они несовместимы. TypeScript поддерживает export = для моделирования традиционной работы CommonJS и AMD.

Синтаксис export = определяет единственный объект, экспортируемый из модуля. Это может быть класс, интерфейс, пространство имен, функция или перечисление.

При экспорте модуля с использованием export =, необходимо использовать специфичные для TypeScript import module = require("module") для импорта модуля.

ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}
export = ZipCodeValidator;
Test.ts
import zip = require("./ZipCodeValidator");

// Some samples to try
let strings = ["Hello", "98052", "101"];

// Validators to use
let validator = new zip();

// Show whether each string passed each validator
strings.forEach((s) => {
  console.log(
    `"${s}" - ${validator.isAcceptable(s) ? "matches" : "does not match"}`
  );
});

Генерация кода для модулей

В зависимости от целевого модуля, указанного во время компиляции, компилятор сгенерирует соответствующий код для Node.js (CommonJS), require.js (AMD), UMD, SystemJS или нативных модулей ECMAScript 2015 (ES6) систем загрузки модулей. Для получения дополнительной информации о том, что делают вызовы define, require и register в сгенерированном коде, обратитесь к документации для каждого загрузчика модулей.

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

SimpleModule.ts
import m = require("mod");
export let t = m.something + 1;
AMD / RequireJS SimpleModule.js
define(["require", "exports", "./mod"], function (require, exports, mod_1) {
  exports.t = mod_1.something + 1;
});
CommonJS / Node SimpleModule.js
var mod_1 = require("./mod");
exports.t = mod_1.something + 1;
UMD SimpleModule.js
(function (factory) {
  if (typeof module === "object" && typeof module.exports === "object") {
    var v = factory(require, exports);
    if (v !== undefined) module.exports = v;
  } else if (typeof define === "function" && define.amd) {
    define(["require", "exports", "./mod"], factory);
  }
})(function (require, exports) {
  var mod_1 = require("./mod");
  exports.t = mod_1.something + 1;
});
System SimpleModule.js
System.register(["./mod"], function (exports_1) {
  var mod_1;
  var t;
  return {
    setters: [
      function (mod_1_1) {
        mod_1 = mod_1_1;
      },
    ],
    execute: function () {
      exports_1("t", (t = mod_1.something + 1));
    },
  };
});
Нативные модули ECMAScript 2015 SimpleModule.js
import { something } from "./mod";
export var t = something + 1;

Простой пример

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

Для компиляции необходимо указать целевой модуль в командной строке. Для Node.js используйте --module commonjs; для require.js используйте --module amd. Например:

tsc --module commonjs Test.ts

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

Validation.ts
export interface StringValidator {
  isAcceptable(s: string): boolean;
}
LettersOnlyValidator.ts
import { StringValidator } from "./Validation";

const lettersRegexp = /^[A-Za-z]+$/;

export class LettersOnlyValidator implements StringValidator {
  isAcceptable(s: string) {
    return lettersRegexp.test(s);
  }
}
ZipCodeValidator.ts
import { StringValidator } from "./Validation";

const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
  isAcceptable(s: string) {
    return s.length === 5 && numberRegexp.test(s);
  }
}
Test.ts
import { StringValidator } from "./Validation";
import { ZipCodeValidator } from "./ZipCodeValidator";
import { LettersOnlyValidator } from "./LettersOnlyValidator";

// Some samples to try
let strings = ["Hello", "98052", "101"];

// Validators to use
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();

// Show whether each string passed each validator
strings.forEach((s) => {
  for (let name in validators) {
    console.log(
      `"${s}" - ${
        validators[name].isAcceptable(s) ? "matches" : "does not match"
      } ${name}`
    );
  }
});

Необязательная загрузка модулей и другие сценарии расширенной загрузки

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

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

END_OF_DOCUMENT_MARKER

Основная идея этого шаблона заключается в том, что оператор import id = require("...") предоставляет нам доступ к типам, экспонируемым модулем. Загрузчик модулей вызывается (через require) динамически, как показано в блоках if ниже. Это использует оптимизацию отбрасывания ссылок, так что модуль загружается только при необходимости. Для работы этого шаблона важно, чтобы символ, определённый через import, использовался только в позициях типов (то есть никогда в позиции, которая была бы выведена в JavaScript).

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

Динамическая загрузка модулей в Node.js
declare function require(moduleName: string): any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
  let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
  let validator = new ZipCodeValidator();
  if (validator.isAcceptable("...")) {
    /* ... */
  }
}
Пример: Динамическая загрузка модулей в require.js
declare function require(
  moduleNames: string[],
  onLoad: (...args: any[]) => void
): void;

import * as Zip from "./ZipCodeValidator";

if (needZipValidation) {
  require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {
    let validator = new ZipCodeValidator.ZipCodeValidator();
    if (validator.isAcceptable("...")) {
      /* ... */
    }
  });
}
Пример: Динамическая загрузка модулей в System.js
declare const System: any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
  System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => {
    var x = new ZipCodeValidator();
    if (x.isAcceptable("...")) {
      /* ... */
    }
  });
}

Работа с другими JavaScript-библиотеками

Для описания формы библиотек, не написанных на TypeScript, нам необходимо объявить API, предоставляемый библиотекой.

Мы называем объявления, не определяющие реализацию, «глобальными». Обычно они определяются в файлах .d.ts. Если вы знакомы с C/C++, вы можете представить их как файлы .h. Давайте рассмотрим несколько примеров.

Глобальные модули

В Node.js большинство задач выполняется путем загрузки одного или нескольких модулей. Мы могли бы определить каждый модуль в своем отдельном файле .d.ts с объявлениями экспорта на верхнем уровне, но удобнее написать их в одном более крупном файле .d.ts. Для этого мы используем конструкцию, похожую на глобальные пространства имён, но используем ключевое слово module и указанное в кавычках имя модуля, которое будет доступно последующему импорту. Например:

node.d.ts (упрощённый фрагмент)
declare module "url" {
  export interface Url {
    protocol?: string;
    hostname?: string;
    pathname?: string;
  }

  export function parse(
    urlStr: string,
    parseQueryString?,
    slashesDenoteHost?
  ): Url;
}

declare module "path" {
  export function normalize(p: string): string;
  export function join(...paths: any[]): string;
  export var sep: string;
}

Теперь мы можем /// <reference> node.d.ts и затем загрузить модули с помощью import url = require("url"); или import * as URL from "url".

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("https://www.typescriptlang.org");

Краткая форма глобальных модулей

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

declarations.d.ts
declare module "hot-new-module";

Все импорты из краткого модуля будут иметь тип any.

import x, { y } from "hot-new-module";
x(y);

Объявления модулей с подстановкой

Некоторые загрузчики модулей, такие как SystemJS и AMD, позволяют импортировать контент, не являющийся JavaScript. Обычно для этого используется префикс или суффикс для указания специальной семантики загрузки. Объявления модулей с подстановкой могут быть использованы для покрытия таких случаев.

declare module "*!text" {
  const content: string;
  export default content;
}
// Some do it the other way around.
declare module "json!*" {
  const value: any;
  export default value;
}

Теперь вы можете импортировать вещи, которые соответствуют "*!text" или "json!*".

import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);

UMD-модули

Некоторые библиотеки предназначены для использования во многих загрузчиках модулей или без загрузки модулей (глобальные переменные). Они известны как UMD-модули. Доступ к этим библиотекам можно получить как через импорт, так и через глобальную переменную. Например:

math-lib.d.ts
export function isPrime(x: number): boolean;
export as namespace mathLib;

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

import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // ERROR: can't use the global definition from inside a module

Также её можно использовать как глобальную переменную, но только внутри скрипта. (Скрипт — это файл без импортов или экспортов.)

mathLib.isPrime(2);

Рекомендации по структурированию модулей

Экспорт как можно ближе к верхнему уровню

Потребители вашего модуля должны иметь как можно меньше трудностей при использовании экспортируемых элементов. Добавление слишком многих уровней вложенности затрудняет работу, поэтому тщательно продумайте, как вы хотите всё структурировать.

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

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

Если вы экспортируете только один class или function, используйте export default

Так же, как «экспорт близко к верхнему уровню» уменьшает сложности для потребителей вашего модуля, так же и введение экспорта по умолчанию. Если основная цель модуля — размещение одного определённого экспорта, то следует рассмотреть экспорт его как экспорт по умолчанию. Это облегчает как импорт, так и фактическое использование импорта. Например:

MyClass.ts

export default class SomeType {
  constructor() { ... }
}

MyFunc.ts

export default function getThing() {
  return "thing";
}

Consumer.ts

import t from "./MyClass";
import f from "./MyFunc";
let x = new t();
console.log(f());

Это оптимально для потребителей. Они могут называть ваш тип как угодно (t в данном случае) и не должны выполнять чрезмерное точечное обращение к вашим объектам.

Если вы экспортируете несколько объектов, поместите их все на верхнем уровне

MyThings.ts

export class SomeType {
  /* ... */
}
export function someFunc() {
  /* ... */
}

И наоборот, при импорте:

Явно указывать импортируемые имена

Consumer.ts

import { SomeType, someFunc } from "./MyThings";
let x = new SomeType();
let y = someFunc();

Используйте шаблон импорта пространства имён, если вы импортируете большое количество элементов

MyLargeModule.ts

export class Dog { ... }
export class Cat { ... }
export class Tree { ... }
export class Flower { ... }

Consumer.ts

import * as myLargeModule from "./MyLargeModule.ts";
let x = new myLargeModule.Dog();

Переэкспорт для расширения

Зачастую вам потребуется расширить функциональность модуля. Общий шаблон JS заключается в дополнении исходного объекта «расширениями», аналогично тому, как работают расширения JQuery. Как мы уже упоминали ранее, модули не «сливаются», как глобальные объекты пространства имён. Рекомендуемым решением является не изменение исходного объекта, а экспорт новой сущности, предоставляющей новую функциональность.

Рассмотрим простую реализацию калькулятора, определённую в модуле Calculator.ts. Модуль также экспортирует вспомогательную функцию для тестирования функциональности калькулятора путём передачи списка входных строк и вывода результата в конце.

Calculator.ts

export class Calculator {
  private current = 0;
  private memory = 0;
  private operator: string;

  protected processDigit(digit: string, currentValue: number) {
    if (digit >= "0" && digit <= "9") {
      return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0));
    }
  }

  protected processOperator(operator: string) {
    if (["+", "-", "*", "/"].indexOf(operator) >= 0) {
      return operator;
    }
  }

  protected evaluateOperator(
    operator: string,
    left: number,
    right: number
  ): number {
    switch (this.operator) {
      case "+":
        return left + right;
      case "-":
        return left - right;
      case "*":
        return left * right;
      case "/":
        return left / right;
    }
  }

  private evaluate() {
    if (this.operator) {
      this.memory = this.evaluateOperator(
        this.operator,
        this.memory,
        this.current
      );
    } else {
      this.memory = this.current;
    }
    this.current = 0;
  }

  public handleChar(char: string) {
    if (char === "=") {
      this.evaluate();
      return;
    } else {
      let value = this.processDigit(char, this.current);
      if (value !== undefined) {
        this.current = value;
        return;
      } else {
        let value = this.processOperator(char);
        if (value !== undefined) {
          this.evaluate();
          this.operator = value;
          return;
        }
      }
    }
    throw new Error(`Unsupported input: '${char}'`);
  }

  public getResult() {
    return this.memory;
  }
}

export function test(c: Calculator, input: string) {
  for (let i = 0; i < input.length; i++) {
    c.handleChar(input[i]);
  }

  console.log(`result of '${input}' is '${c.getResult()}'`);
}

Вот простой тест для калькулятора, использующий экспортированную функцию test.

TestCalculator.ts

import { Calculator, test } from "./Calculator";

let c = new Calculator();
test(c, "1+2*33/11="); // prints 9

Теперь, чтобы расширить это, добавив поддержку ввода чисел в системах счисления, отличных от десятичной, создадим ProgrammerCalculator.ts

ProgrammerCalculator.ts

import { Calculator } from "./Calculator";

class ProgrammerCalculator extends Calculator {
  static digits = [
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    "A",
    "B",
    "C",
    "D",
    "E",
    "F",
  ];

  constructor(public base: number) {
    super();
    const maxBase = ProgrammerCalculator.digits.length;
    if (base <= 0 || base > maxBase) {
      throw new Error(`base has to be within 0 to ${maxBase} inclusive.`);
    }
  }

  protected processDigit(digit: string, currentValue: number) {
    if (ProgrammerCalculator.digits.indexOf(digit) >= 0) {
      return (
        currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit)
      );
    }
  }
}

// Export the new extended calculator as Calculator
export { ProgrammerCalculator as Calculator };

// Also, export the helper function
export { test } from "./Calculator";

Новый модуль ProgrammerCalculator экспортирует форму API, аналогичную исходному модулю Calculator, но не изменяет какие-либо объекты в исходном модуле. Вот тест для нашего класса ProgrammerCalculator:

TestProgrammerCalculator.ts

import { Calculator, test } from "./ProgrammerCalculator";

let c = new Calculator(2);
test(c, "001+010="); // prints 3

Не использовать пространства имён в модулях

При первом переходе на модульную организацию, распространённой тенденцией является обертывание экспортов дополнительным слоем пространств имён. Модули имеют свою область видимости, и только экспортированные объявления видны снаружи модуля. Учитывая это, пространства имён мало, если вообще, не приносят никакой пользы при работе с модулями.

В организационном плане пространства имён удобны для группировки логически связанных объектов и типов в глобальной области видимости. Например, в C# вы найдёте все типы коллекций в System.Collections. Организуя наши типы в иерархических пространствах имён, мы обеспечиваем хороший опыт «обнаружения» для пользователей этих типов. Модули, с другой стороны, уже присутствуют в файловой системе, что необходимо. Мы должны решать их по пути и имени файла, так что у нас есть логическая схема организации. У нас может быть папка /collections/generic/ с модулем списка в ней.

Пространства имён важны для предотвращения конфликтов имён в глобальной области видимости. Например, у вас могут быть My.Application.Customer.AddForm и My.Application.Order.AddForm — два типа с одинаковым именем, но в разных пространствах имён. Однако, это не проблема с модулями. В пределах модуля нет разумных причин иметь два объекта с одинаковым именем. С точки зрения потребления, потребитель любого данного модуля может выбрать имя, которое он будет использовать для обращения к модулю, поэтому случайные конфликты имён невозможны.

Для получения дополнительной информации о модулях и пространствах имён, см. Пространства имён и модули.

Красные флаги

Все перечисленные ниже — красные флаги для структурирования модулей. Дважды проверьте, что вы не пытаетесь применять пространства имён к вашим внешним модулям, если что-либо из этого относится к вашим файлам:

  • Файл, единственным объявлением на верхнем уровне которого является export namespace Foo { ... } (удалите Foo и перенесите всё на более высокий уровень)
  • Несколько файлов, у которых одинаковое export namespace Foo { на верхнем уровне (не думайте, что они объединятся в один Foo!)

© 2012-2023 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/modules.html

Spec-Zone.ru

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