Модули

Глобальный модуль

По умолчанию, когда вы начинаете писать код в новом файле TypeScript, он находится в глобальном пространстве имен. Например, следующий код в foo.ts:

const foo = 123;

Если вы создадите новый файл bar.ts в том же проекте, система типов TypeScript позволит вам использовать переменную foo, как если бы она была доступна глобально:

const bar = foo; // allowed

Использование пространства глобальных переменных опасно, поскольку оно конфликтует с переменными в файле. Мы рекомендуем использовать файловый модуль, указанный ниже.

Файловый модуль

Он также называется внешним модулем. Если вы включите import или export на корневом уровне вашего файла TypeScript, он создаст локальную область в этом файле. Поэтому нам нужно изменить вышеприведенный foo.ts к следующему виду (обратите внимание на использование export):

export const foo = 123;

В глобальном пространстве имен у нас больше нет переменной foo, что можно доказать, создав новый файл bar.ts:

const bar = foo; // ERROR: "cannot find name 'foo'"

Если вы хотите использовать переменные из foo.ts в bar.ts, вы должны явно импортировать его и обновить bar.ts следующим образом:

import { foo } from './foo';
const bar = foo; // allow

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

Детали файлового модуля

Файловый модуль обладает мощными возможностями и удобством использования. Здесь мы обсудим его возможности и некоторые применения.

Пояснения: commonjs, amd, es modules, others

Во-первых, нам нужно прояснить несоответствия этих модульных систем. Я предоставлю вам мой текущий совет и некоторые проблемы.

Вы можете скомпилировать TypeScript в разные типы модулей JavaScript, основываясь на разных параметрах module. Вот некоторые вещи, которые вы можете игнорировать:

  • AMD: не используйте его, он работает только в браузере;

  • SystemJS: Это был хороший эксперимент, и его заменили модули ES;

  • Модуль ES: не готов.

Хорошей идеей является использование параметра module: commonjs вместо указанных выше.

Как писать модули TypeScript также сбивает с толку. Сегодня мы должны делать так:

  • Отказаться от использования синтаксиса import / require, т.е.

    import foo = require ('foo')

  • Вместо этого используйте синтаксис модуля ES

Круто, давайте посмотрим на синтаксис модуля ES.

Импортируйте, экспортируйте и записывайте модули, используя параметр module: commonjs и используя синтаксис модуля ES.

Синтаксис модуля ES

  • Используйте ключевое слово export для экспорта переменной (или типа):

// foo.ts
export const someVar = 123;
export type someType = {
  foo: string;
};
  • Экспорт переменной или типа в отдельном операторе export:

// foo.ts
const someVar = 123;
type someType = {
  type: string;
};

export { someVar, someType };
  • Вы также можете переименовать переменные для экспорта:

// foo.ts
const someVar = 123;
export { someVar as aDifferentName };
  • Используйте ключевое слово import для импорта переменной или типа:

// bar.ts
import { someVar, someType } from './foo';
  • Переименуйте импортированные переменные или типы:

// bar.ts
import { someVar as aDifferentName } from './foo';
  • Помимо указания выходного значения для загрузки, вы также можете использовать глобальную загрузку, то есть указать объект со звездочкой (*), и все выходные значения будут загружены в этот объект:

// bar.ts
import * as foo from './foo';
// Вы можете использовать `foo.someVar` и` foo.someType` и
// любые другие переменные или типы, экспортированные из `foo`
  • Импортируйте файл только для побочного эффекта с помощью одного оператора import:

import 'core-js'; // стандартная библиотека polyfill
  • Общий экспорт после импорта из других модулей:

export * from './foo';
  • Реэкспорт только конкретных из другого модуля

export { someVar } from './foo';
  • Реэкспорт только конкретных из другого модуля с переименованием

export { someVar as aDifferentName } from './foo';

Импорт / экспорт по умолчанию

Я не люблю использовать экспорт по умолчанию, тем не менее вот синтаксис экспорта по умолчанию:

  • Использование export default :

    • Перед переменной (не нужно использовать let / const / var);

    • Перед функцией;

    • Перед классом.

// some var
export default someVar = 123;

// some function
export default function someFunction() {}

// some class
export default class SomeClass {}
  • При импорте используется import someName from 'someModule (вы можете указать любое имя при необходимости):

import someLocalNameForThisFile from './foo';

Модульные пути

Если вам нужно использовать параметр moduleResolution: node, вам нужно добавить его в файл конфигурации. Если вы используете опцию module: commonjs, то по умолчанию будет включен moduleResolution: node.

Есть два разных вида модулей:

  • Относительный путь к модулю (путь начинается с .(точка), например: ./someFile или ../../someFolder/someFile и т. д.);

  • Другие модули динамического поиска (например: core-js, typestyle, react или даже react/core ​​и т. д.).

Основное отличие состоит в том, как модуль расположен в файловой системе.

Я буду использовать концептуальный термин place - я объясню его, когда упомяну шаблон поиска.

Относительный путь модуля

Это просто, просто следуйте по относительному пути:

  • Если файл bar.ts содержит import * as foo from './foo', то при таком построении файл foo, должен находиться в той же папке;

  • Если файл bar.ts содержит import * as foo from '../foo', то при таком построении файл foo, должен находиться в родительском каталоге;

  • Если файл bar.ts содержит import * as foo from '../someFolder/foo', папка, в которой находится файл foo (someFolder), должна находиться находиться в родительском каталоге.

Или вы можете подумать о других сценариях импорта относительных путей. 😀

Динамический поиск

Когда путь импорта не является относительным путем, в силу вступает поиск модуля nodejs . Я приведу простой пример ниже:

  • Когда вы используете import * as foo from 'foo', модули ищутся в следующем порядке:

    • ./node_modules/foo

    • ../node_modules/foo

    • ../../node_modules/foo

    • И так до корня системы

  • Когда вы используете import * as foo from 'thing/foo', содержимое будет искать в следующем порядке:

    • ./node_modules/something/foo

    • ../node_modules/something/foo

    • ../../node_modules/something/foo

    • И так до корня системы

Что такое `place`

Когда я упоминаю проверяемое place, я хочу сказать, что в этом place TypeScript проверит следующее (например, местоположение foo):

  • Если это place представляет файл, такой как: foo.ts, ура!

  • В противном случае, если это place является папкой и существует файл foo/index.ts, ура!

  • В противном случае, если это place является папкой и существует файл foo/package.json, и в нем есть types указывающий на типы, ура!

  • В противном случае, если это place является папкой и в ней существует файл package.json с указанием main параметра в этом файле, ура!

Под файлом я имею в виду .ts / .d.ts и .js.

Вот и все, теперь вы эксперт по поиску модулей (это немалый успех).

Переопределить тип динамического поиска

В вашем проекте вы можете объявить глобальный модуль, declate module 'somePath' для решения проблемы поиска пути к модулю:

// global.d.ts
declare module 'foo' {
  // some variable declarations
  export var bar: number;
}

а потом:

// anyOtherTsFileInYourProject.ts
import * as foo from 'foo';
// TypeScript предполагает (без поиска), что
// foo это { bar: number }

import/require только для типов

Следующий синтаксис импорта:

import foo = require('foo');

На самом деле он делает только две вещи:

  • Импортировать всю информацию о типе модуля foo;

  • Определите зависимости времени выполнения модуля foo

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

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

Пример 1

import foo = require('foo');

сгенерирует JavaScript:

Это правильно, пустой файл, который не используется.

Пример 2

import foo = require('foo');
let bar: foo;

сгенерирует JavaScript:

let bar;

Это потому, что foo (или любой другой атрибут, такой как foo.bas) не используется в качестве переменной.

Пример 3

import foo = require('foo');
const bar = foo;

сгенерирует JavaScript (при условии, что module: commonjs):

const foo = require('foo');
const bar = foo;

Это потому, что foo используется как переменная.

Пример использования: ленивая загрузка

Вывод типа должен быть сделан заранее. Это означает, что если вы хотите использовать какой-либо тип из файла foo в файле , вам нужно будет сделать:

import foo = require('foo');
let bar: foo.SomeType;

Однако в некоторых сценариях вы хотите загружать модуль foo только тогда, когда это необходимо, и вам нужно использовать имя импортированного модуля только в аннотации типа, а не в переменной. Они будут удалены при компиляции в JavaScript. Затем вы можете вручную импортировать нужные вам модули.

import foo = require('foo');

export function loadFoo() {
  // Это ленивая загрузка foo, оригинальная загрузка используется только для аннотаций типов
  const _foo: typeof foo = require('foo');
  // Теперь вы можете использовать `_foo` вместо` foo` в качестве переменной
}

Не менее простой модуль amd (с использованием requirejs):

import foo = require('foo');

export function loadFoo() {
  // Это ленивая загрузка foo, оригинальная загрузка используется только для аннотаций типов
  require(['foo'], (_foo: typeof foo) => {
    // Теперь вы можете использовать `_foo` вместо` foo` в качестве переменной
  });
}

Они обычно используются в следующих сценариях:

  • В веб-приложении, когда вы загружаете JavaScript по определенному маршруту;

  • В приложениях nodejs, когда вы хотите загрузить только определенные модули, чтобы ускорить запуск.

Пример использования: нарушение круговых зависимостей

Как и в случае использования отложенной загрузки, некоторые загрузчики модулей (commonjs / node и amd / requirejs) плохо обрабатывают циклические зависимости. В этом случае, с одной стороны, мы используем ленивый код загрузки, а с другой - полезно предварительно загрузить модуль.

Пример использования: обязательно импортируйте

Иногда вы хотите загрузить файл только для побочного эффекта (например, модуль может зарегистрироваться в некоторой библиотеке, такой как дополнения CodeMirror и т. д.). Однако, если вы просто выполняете import/require , скомпилированный JavaScript не будет содержать зависимости от модуля, и ваш загрузчик модулей (например, веб-пакет) может полностью игнорировать импорт. В таких случаях вы можете использовать переменную sureImport, чтобы гарантировать, что скомпилированный JavaScript принимает зависимость от модуля, например:

import foo = require('./foo');
import bar = require('./bar');
import bas = require('./bas');

const ensureImport: any = foo || bar || bas;

global.d.ts

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

Однако, если в вашей команде есть начинающие TypeScript разработчики, вы можете предоставить им файл global.d.ts для размещения некоторых интерфейсов или типов в глобальном пространстве имен. Эти определенные интерфейсы и типы могут использоваться во всех ваших TypeScript файлах.

Для любого кода, который необходимо скомпилировать в JavaScript, мы настоятельно рекомендуем поместить его в файловый модуль.

  • global.d.ts - отличный способ расширить lib.d.ts, если вам нужно.

  • Когда вы переходите с JS на TS, определение модуля объявлений 'some-library-you-dont-care-to-get-defs-for' поможет вам быстро начать работу.

Last updated

Was this helpful?