Перейти к содержимому
Теория · Проект на TypeScript · урок 3 из 10

import type

Иногда соседней комнате нужна не сама вещь, а только её размеры. Чтобы сшить чехол для дивана, диван не носят по дому: хватит листка с его меркой. Листок нужен, пока шьют, а когда чехол готов, листок выбрасывают.

Типы в модулях — такие же мерки. Пусть в программе три файла:

  • user.ts экспортирует интерфейс User с именем и фамилией;
  • format.ts экспортирует функцию formatName(user: User): string, которая склеивает имя и фамилию;
  • main.ts берёт и интерфейс, и функцию.

Функция formatName работает при запуске: её вызывают, и она возвращает строку. А интерфейс User нужен только tsc, чтобы проверить код. Вот user.ts:

TypeScript
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.

ВживуюЧто tsc оставит от модуляшаг 1 из 6

Строка import type исчезла целиком: она приносила только тип User.

Подсветка показывает строку TypeScript и строку JavaScript, которая из неё получилась

Что остаётся в 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 записывает ответ прямо в строке, и это даёт три вещи:

  1. Читатель кода сразу видит, что строка не доживёт до JavaScript.
  2. tsc следит за обещанием: имя из import type нельзя использовать как значение.
  3. Инструмент, который видит один файл, понимает, что строку можно убрать. Так работает node main.ts: он вырезает типы, не заглядывая в другие файлы. Обычный импорт User он оставит, и при запуске Node станет искать в модуле значение User, которого там нет: программа остановится с ошибкой.

Второй пункт в коде: функцию взяли через import type, а потом вызвали.

TypeScript
import type { formatName } from "./format.js";

const text = formatName({ first: "Anna", last: "Ivanova" }); // ✗
TEXT
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, и читает его.

Частая ошибка

Путь написали без расширения:

TypeScript
import { formatName } from "./format"; // ✗

console.log(formatName({ first: "Anna", last: "Ivanova" }));

tsc main.ts промолчит: с настройками по умолчанию он находит format.ts и по такому пути. А node main.js остановится:

TEXT
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../format' imported from .../main.js

Node.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.