Menu

Вывод типов в TypeScript: когда писать аннотации

Большинство типов TypeScript выводит сам из значений, которые вы пишете. Разберём, что выводится для переменных, let и const, объектов, массивов и возвращаемых значений, как колбэки получают типы из контекста и где аннотация всё же нужна.

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

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

Комментарии показывают, что отображает редактор при наведении на каждое имя. Последняя строка это ошибка компиляции (TS2322), хотя count никогда не аннотировалась; // @ts-expect-error помечает её как ожидаемую, чтобы остальной блок запустился.

let и const: расширение литералов

const никогда не меняется, поэтому TypeScript даёт ей самый узкий тип: само литеральное значение. let получает более широкий тип, потому что её могут переприсвоить позже.

let a = "left";    // string
const b = "left";  // "left"
let c = 42;        // number
const d = 42;      // 42
const e = true;    // true

Это важно, когда функция принимает только определённые значения:

index.ts(7,6): error TS2345: Argument of type 'string' is not assignable to parameter of type '"left" | "right"'.

Исправить можно через const или аннотацию переменной: let dir: "left" | "right" = "left". Литеральным типам посвящена отдельная страница, литеральные типы.

Объекты и массивы расширяют свои элементы

Свойства объектного литерала изменяемы, поэтому они расширяются так же, как let, даже если сам объект объявлен через const. С элементами массива то же самое. as const делает всё readonly и сохраняет литеральные типы.

Вывод возвращаемого типа

Возвращаемый тип функции выводится из её операторов return. Если их несколько, результат это объединение их типов.

Для большинства функций выведенного возвращаемого типа достаточно. Пишите возвращаемый тип явно у экспортируемых или публичных функций, когда хотите, чтобы сигнатура была контрактом: тогда изменение внутри тела, которое возвращает другой тип, упадёт на самой функции, а не где-то у далёкого вызывающего кода.

Контекстная типизация: колбэки получают типы

Параметры не выводятся из того, как функция вызывается. Есть два исключения. Параметр со значением по умолчанию получает тип этого значения (function retry(times = 3) делает times типом number). И функция, написанная там, где уже ожидается функциональный тип, получает типы параметров из этого контекста, поэтому параметрам колбэков аннотация не нужна.

То же работает для forEach, filter, reduce, колбэков Promise, обработчиков событий и любой функции, которую вы передаёте в параметр с объявленным функциональным типом.

Параметрам нужны аннотации

Без значения по умолчанию или такого контекста TypeScript не смотрит, как вызывается функция, чтобы угадать типы её параметров. При включённом strict параметр без аннотации даёт ошибку TS7006:

index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.

Напишите function double(x: number), и блок напечатает 42, а возвращаемый тип number выведется из x * 2.

Наилучший общий тип

Для литерала массива со значениями разных видов TypeScript выводит массив из объединения их типов. Общий базовый тип, который вы не упомянули, он не ищет.

Единственный случай, за которым стоит следить, это пустой массив. const list = [] начинается как «эволюционирующий» массив, тип которого растёт с каждым push; аннотация const list: string[] = [] сразу обозначает намерение и читается яснее.

Когда писать аннотацию

СитуацияАннотировать?
Переменная инициализирована значениемНет, тип задаёт значение
Переменная объявлена без значения (let result;)Да, let result: string;
Параметры функцийДа (кроме колбэков с контекстом или значения по умолчанию)
Возвращаемый тип небольшой внутренней функцииПо желанию
Возвращаемый тип экспортируемой или публичной функцииРекомендуется, как контракт
Пустой массив или объект, который заполняется позжеДа, const ids: number[] = []
Значение должно сохранить литеральные типыИспользуйте as const
Значение должно соответствовать типу, но сохранить свой выведенный типИспользуйте satisfies

Быстрее всего увидеть выведенный тип, наведя курсор на имя в редакторе. Без редактора присвойте значение переменной неверного типа, например const probe: boolean = mixed;, и сообщение компилятора об ошибке назовёт выведенный тип.

Часто задаваемые вопросы

Что такое вывод типов в TypeScript?

Это когда компилятор сам определяет тип, который вы не написали. let count = 0 даёт count тип number, а функция, которая возвращает a + b для двух чисел, получает возвращаемый тип number. Выведенный тип проверяется точно так же, как написанный.

Нужно ли аннотировать каждую переменную в TypeScript?

Нет. Аннотируйте параметры функций (они не выводятся из вызовов, только из значения по умолчанию или из контекста колбэка), возвращаемые типы экспортируемых функций, если нужен стабильный API, и переменные, объявленные без значения. Остальное пусть выводит TypeScript: const name: string = "Ada" повторяет то, что компилятор и так знает.

Почему const выводит литеральный тип, а let выводит string?

const нельзя переприсвоить, поэтому const dir = "left" получает литеральный тип "left". В let позже может оказаться любая другая строка, поэтому let dir = "left" расширяется до string. То же расширение происходит со свойствами объектного литерала, потому что свойства можно переприсваивать; as const его отключает.

Почему TypeScript пишет, что параметр неявно имеет тип 'any'?

Это ошибка TS7006 от noImplicitAny, которая входит в strict. TypeScript не выводит типы параметров из того, как функция вызывается, поэтому параметр без типа был бы any. Добавьте аннотацию, function double(x: number), или передайте функцию туда, где её тип известен из контекста, например как колбэк в map.

Как посмотреть тип, который вывел TypeScript?

Наведите курсор на имя в редакторе (VS Code и большинство редакторов показывают выведенный тип во всплывающей подсказке). В коде можно присвоить значение переменной заведомо неверного типа: компилятор напечатает выведенный тип в сообщении об ошибке.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ