import type
Иногда соседней комнате нужна не сама вещь, а только её размеры. Чтобы сшить чехол для дивана, диван не носят по дому: хватит листка с его меркой. Листок нужен, пока шьют, а когда чехол готов, листок выбрасывают.
Типы в модулях — такие же мерки. Пусть в программе три файла:
user.tsэкспортирует интерфейсUserс именем и фамилией;format.tsэкспортирует функциюformatName(user: User): string, которая склеивает имя и фамилию;main.tsберёт и интерфейс, и функцию.
Функция formatName работает при запуске: её вызывают, и она возвращает строку. А интерфейс User нужен только tsc, чтобы проверить код. Вот user.ts:
export interface User {
first: string;
last: string;
}Импорт только типов
Для мерок в TypeScript есть отдельная запись — импорт типов: import type { User } from "./user.js";. Разберём её по частям:
import type— импорт, который приносит только типы;{ User }— имена типов, их может быть несколько;from "./user.js"— модуль, который эти типы экспортирует.
Через import type берут интерфейсы и псевдонимы типов. Взять так можно и класс, но тогда он будет только типом: создать объект через new не получится.
Посмотрим, что tsc сделает с файлом main.ts, где есть оба вида импорта. Нажимайте «Следующий шаг» и следите, какие строки дойдут до JavaScript.
Строка import type исчезла целиком: она приносила только тип User.
Что остаётся в JavaScript
После компиляции в main.js:
- нет строки
import type— она исчезла целиком; - нет интерфейсов и псевдонимов типов, таких как
Badge; - нет аннотаций у параметров, результатов и переменных;
- остались функции, классы и
enum: они работают при запуске; - остались обычные импорты значений, как
import { formatName }.
Здесь аналогия с меркой перестаёт работать: листок кто-то должен выбросить, а типы из JavaScript tsc стирает сам.
Ещё одна мелочь: в начале main.js нет строки "use strict";. Модуль JavaScript и так всегда работает в строгом режиме, поэтому tsc её не дописывает.
Зачем писать type
Обычный импорт import { User } from "./user.js" компилятор тоже уберёт, если User встречается в файле только как тип. Но чтобы это понять, ему нужно заглянуть в user.ts и проверить каждое место с этим именем. import type записывает ответ прямо в строке, и это даёт три вещи:
- Читатель кода сразу видит, что строка не доживёт до JavaScript.
tscследит за обещанием: имя изimport typeнельзя использовать как значение.- Инструмент, который видит один файл, понимает, что строку можно убрать. Так работает
node main.ts: он вырезает типы, не заглядывая в другие файлы. Обычный импортUserон оставит, и при запуске Node станет искать в модуле значениеUser, которого там нет: программа остановится с ошибкой.
Второй пункт в коде: функцию взяли через import type, а потом вызвали.
import type { formatName } from "./format.js";
const text = formatName({ first: "Anna", last: "Ivanova" }); // ✗error TS1361: 'formatName' cannot be used as a value because it was imported using 'import type'.tsc напоминает: такой импорт исчезнет из JavaScript, и функции formatName в main.js не будет.
Чем import type отличается от обычного импорта, часто спрашивают на собеседовании. Ответ: import type приносит только типы и целиком исчезает из JavaScript, а импорт значения остаётся и при запуске загружает модуль.
Расширение .js в пути
tsc не меняет путь после from: строка "./format.js" попадает в main.js как есть. При запуске main.js загрузит соседний файл format.js — тот, что tsc запишет из format.ts. Поэтому в исходнике путь называет будущий файл .js. Проверяя типы, tsc знает, что за ./format.js стоит format.ts, и читает его.
Частая ошибка
Путь написали без расширения:
import { formatName } from "./format"; // ✗
console.log(formatName({ first: "Anna", last: "Ivanova" }));tsc main.ts промолчит: с настройками по умолчанию он находит format.ts и по такому пути. А node main.js остановится:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../format' imported from .../main.jsNode.js ищет файл ровно по пути из main.js, а файла format без расширения нет: рядом лежит format.js. Вместо точек Node.js печатает полный путь к папке проекта. Допишите расширение: from "./format.js".
Главное
import type { User } from "./user.js"приносит только типы и целиком исчезает из JavaScript.- Из JavaScript исчезают все типы: интерфейсы, псевдонимы, аннотации; функции, классы,
enumи импорты значений остаются. - Имя из
import typeнельзя использовать как значение:tscответит ошибкойTS1361. tscне меняет путь импорта, поэтому в нём пишут расширение будущего файла:./format.js.