Эта история является частью серии, которая была начата здесь:

День 0 — «Как запустить TypeScript»

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

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

Модульное программирование — это, пожалуй, самый мощный способ создания поддерживаемого кода с четким разделением задач. Его влияние простирается даже дальше, позволяя сборщикам, таким как webpack, создавать высокооптимизированные активы для браузеров.

Согласно определению Typescript, любой файл, который использует оператор верхнего уровня import или export, рассматривается как модуль.

Экспорт:

Список наиболее распространенных операторов экспорта приведен здесь:

Видите ли, почти каждое выражение можно экспортировать. Вы поймете больше деталей, если посмотрите на потребительскую сторону (импорт). Примечательно еще:

export default ...

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

Также у нас есть синтаксис объекта:

export {e}
export {f}

Это напрямую объединяет значения e и f в качестве свойств экспортируемого объекта.

Импорт:

Теперь мы собираемся взглянуть на сторону «импорта», чтобы получить полную картину. Если мы предположим, что экспорт был сохранен в файле с именем data.service.ts, то в другом файле мы можем использовать этот экспорт с помощью импорта, как здесь:

Начнем с theDefault. Это импортирует export default d, и, как вы видите, импорт не требуется для использования имени d. Это позволяет по-разному обрабатывать экспортированное значение по умолчанию при импорте.

Другие значения просто импортируются путем деструктурирования объекта экспорта, то есть:

import { a as aa, b, h, e, f, fn } from './data.service';

Это делает b, h, e, f, fn все доступными в текущем контексте. Переменные, импортированные по имени, можно переименовывать, чтобы избежать конфликтов имен, например, для a, которое переименовывается в aa by a as aa.

Другой способ — импортировать весь экспортируемый объект, как это сделано здесь:

import * as dataService from './data.service';

Это делает все экспортированные переменные доступными для объекта, который мы назвали dataService. Таким образом, доступ, например, к a можно сделать так:

dataService.a

При условии, что импорт был сохранен в файле с именем data.controller.ts, пример можно запустить, поместив в index.ts следующее содержимое:

В качестве альтернативы мы могли бы запустить файл импорта напрямую, но здесь я хочу отметить возможность импорта всего файла без указания каких-либо переменных. Приведенная выше строка вызывает импорт модуля data.controller , но не помещает переменные в текущую область видимости. При первом появлении такого оператора выполняется код в data.controller.ts.

Выполнение этого ( npm start ) выводит:

a is 1
b is 2
default is 3
e is 4
f is 5
calling the function fn: undefined
1

Сфера:

Как правило, файл JavaScript (помните, что TypeScript компилируется в JavaScript) выполняется в глобальной области видимости. Это означает, что все объявления верхнего уровня присоединены к глобальному объекту.

Для модулей это не так — и это делает их невероятно полезными. Модуль всегда имеет свою область действия, которая отделена от остальной части кодовой базы. Только экспортированные переменные могут быть доступны при импорте в другие модули.

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

export const h = { prop: 6 };

предоставляет все свойства более глубокого уровня по одной и той же ссылке. Вы бы импортировали h в какой-то другой модуль и изменили бы значение prop

import { h } from './data.service'
h.prop = 7;

тогда другие модули, которые импортируют h, также увидят эти изменения.

Разрешение:

То, как модули разрешаются из URL-адресов в операторах импорта, можно настроить с помощью различных настроек в tsconfig.json. Я не хочу сейчас вдаваться во все эти детали, а сконцентрируюсь на значениях по умолчанию и оставлю конкретные настройки для последующих глав, когда в этом возникнет необходимость.

Когда модуль важен, например (относительный путь)

import './dir/my-module'

то компилятор ищет следующим образом:

  1. ./dir/my-module.ts (разрешение по файлу)
  2. ./dir/my-module.d.ts (разрешение будет определением типа)
  3. ./dir/my-module/package.json (разрешение по определению типа в package.json)
  4. ./dir/my-module/index.ts (разрешение по индексу)
  5. ./dir/my-module/index.d.ts (разрешение по определению типа индекса)

Когда модуль импортируется как (не относительный путь)

import 'my-module'

выполняются аналогичные шаги, описанные выше, но он просматривает папки, расположенные в «/node_modules/dir/…». Это означает, что он пытается найти модуль относительно папки node_modules на корневом уровне. Более того, в этом случае он ищет в папке ‘/node_modules/@types/dir/my-module.d.ts’ любые определения типов.

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

Предположим, у нас есть папка с именем services, содержащая службы data.service.ts и another.service.ts. Оба могут содержать объявления классов, например:

export class DataService{
...
}

и

export class AnotherService{
...
}

Далее добавляем в эту же папку файл с именем index.ts с содержимым:

import { AnotherService } from './another.service';
import { DataService } from './data.service';
export const dataService = new DataService;
export const anotherService = new AnotherService;

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

В нашем верхнем уровне index.ts мы могли бы импортировать эти экземпляры следующим образом:

import { anotherService, dataService } from './src/modules/example3/services';
dataService   // do something with it
anotherService  // do something with it

Этот шаблон позволяет нам обслуживать экземпляры классов, как в шаблоне singleton, и работает, потому что модуль '../services’ разрешается через его файл index.ts.

Мы рассмотрели основные части модулей, которые, в частности, обеспечивают важную основу для остальной части этой серии. Модули есть везде, и важно понимать их использование. Модульное программирование не ограничивается только TypeScript. Такие языки, как JavaScript, Go, Python, C++, .. также приняли эту очень полезную концепцию.

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

Спасибо за чтение!