Файлы .d.ts и @types
В доме стоит прибор, на кнопках которого нет надписей. Пользоваться им можно, но только наугад. Выручает инструкция: какая кнопка что делает и что прибор выдаст в ответ. Сам прибор инструкция не меняет, она только его описывает.
Библиотека на JavaScript — такой прибор. В ней есть функции, но нет типов, и tsc не знает, какие аргументы им передавать. Возьмём популярную библиотеку lodash и её функцию chunk, которая режет массив на части заданного размера. Сначала библиотеку ставят командой npm, а npm кладёт пакеты в папку node_modules проекта:
$ npm install lodashТеперь импортируем chunk:
import { chunk } from "lodash"; // ✗
console.log(chunk([1, 2, 3, 4, 5], 2));error TS7016: Could not find a declaration file for module 'lodash'. '.../node_modules/lodash/lodash.js' implicitly has an 'any' type.
Try `npm i --save-dev @types/lodash` if it exists or add a new declaration (.d.ts) file containing `declare module 'lodash';`Давайте разберём сообщение:
tscнашёлlodashв папкеnode_modules, но там только JavaScript, а описания типов нет. Вместо точекtscпечатает полный путь к папке проекта.- Без описания все функции библиотеки получили бы тип
any, а строгий режим этого не разрешает. - Вторая строка подсказывает два выхода: поставить пакет
@types/lodashили написать файл объявлений самому.
Файл объявлений
Файл объявлений — это файл с расширением .d.ts, в котором есть только типы: имена функций, их параметры и результаты, но ни одного тела. Допустим, в проекте остался старый format.js на JavaScript с функцией formatPrice(price). Рядом с ним кладут format.d.ts:
export declare function formatPrice(price: number): string;Разберём строку по частям:
declareзначит «такая функция есть, но её код в другом месте»: тела нет, и строка кончается точкой с запятой;exportговорит, что функцию экспортирует модульformat.js;(price: number): string— та самая надпись на кнопке: что передать и что получить.
Теперь main.ts пишет import { formatPrice } from "./format.js" и получает типы из format.d.ts, а при запуске Node.js выполняет format.js. Вызов formatPrice("100") компилятор остановит ошибкой TS2345, как будто функция написана на TypeScript.
Помните lib.dom.d.ts из первого урока темы? Это тоже файл объявлений: в нём описаны имена браузера, а их код живёт в самом браузере.
Пакеты @types
Для популярных библиотек объявления уже написаны. Сообщество собирает их в репозитории DefinitelyTyped и публикует отдельными пакетами @types/<имя>:
$ npm install --save-dev @types/lodashЧто важно знать о пакетах @types:
tscнаходит их сам в папкеnode_modules/@types: код по-прежнему пишетfrom "lodash", а импортировать из@types/lodashничего не нужно;- после установки
chunk([1, 2, 3, 4, 5], "2")не скомпилируется: размер части — число, а"2"— строка; - многие библиотеки везут свои файлы
.d.tsв самом пакете, напримерaxios, и пакет@typesим не нужен; - флаг
--save-devставит пакет только для разработки: типы нужныtsc, а не программе при запуске.
Откуда берутся пакеты @types и как TypeScript их находит, часто спрашивают на собеседовании. Ответ: их пишет сообщество в DefinitelyTyped, а tsc сам находит нужный пакет в node_modules/@types, когда код импортирует библиотеку; из @types ничего не импортируют.
declare module
Бывает, что типов нет ни в пакете, ни в @types. Например, у маленькой внутренней библиотеки компании tiny-slug, которая превращает заголовок в кусок адреса страницы. Тогда объявления пишут сами, в своём файле .d.ts, например src/types.d.ts:
declare module "tiny-slug" {
export function slug(text: string): string;
}declare module "tiny-slug" описывает весь пакет, а внутри фигурных скобок — то, что он экспортирует. Файл лежит в src и попадает в проверку вместе с остальными, поэтому import { slug } from "tiny-slug" получает типы, и slug(42) уже не скомпилируется.
Короткую запись подсказывает само сообщение TS7016: declare module "tiny-slug"; объявляет пакет без подробностей, и всё, что из него импортируют, получит тип any. Это быстрый выход, но проверок для такого пакета не будет.
Здесь аналогия с инструкцией перестаёт работать. Настоящую инструкцию пишет производитель прибора. Объявления может написать кто угодно, и tsc им верит: ошибётесь в .d.ts — tsc пропустит неверный вызов.
Частая ошибка
Импортировали функцию из пакета с типами:
import { chunk } from "@types/lodash"; // ✗error TS6137: Cannot import type declaration files. Consider importing 'lodash' instead of '@types/lodash'.В @types/lodash лежит только описание, кода библиотеки там нет. Импортируйте из самой библиотеки, from "lodash", а типы tsc возьмёт из @types сам.
Главное
- Файл
.d.tsописывает JavaScript без типов: в нём только объявления сdeclare, без тел. - Пакеты
@types/<имя>из DefinitelyTypedtscнаходит вnode_modules/@typesсам, импортировать из них не нужно. - Библиотеке, которая везёт свои файлы
.d.ts, пакет@typesне нужен. - Для пакета без типов объявления пишут сами:
declare module "имя" { … }в своём файле.d.ts.