И any, и unknown принимают любое значение. Разница в том, что можно делать со значением дальше: any позволяет что угодно и ничего не проверяет, а unknown почти ничего не позволяет, пока вы не докажете, что это за значение.
Без строки @ts-expect-error вызов u.toUpperCase() был бы ошибкой компиляции TS18046. Проверка typeof сужает u до string, и внутри этого блока доступны все методы строки.
any и unknown коротко
any | unknown | |
|---|---|---|
| Принимает любое значение | да | да |
Присвоить в string, number, ... | да, без проверки | нет (TS2322) |
| Прочитать свойство, вызвать метод | да, без проверки | нет (TS18046) |
| Вызвать как функцию | да | нет |
Арифметика и сравнение (x * 2, x + 1, x < 5) | да | нет (TS18046) |
| Нужна проверка перед использованием | нет | да (typeof, instanceof, in, защитник типа) |
| Влияние на проверку типов | отключена для этого значения и всего, к чему оно прикасается | сохраняется |
В терминах теории типов unknown это верхний тип: любой тип присваивается ему, а сам он присваивается только unknown и any. any это лазейка, которая присваивается в обе стороны, всему, кроме never.
any отключает проверку типов
Значению с типом any доверяют вслепую. Компилятор принимает опечатки, неверные типы и отсутствующие свойства, и ошибки всплывают уже во время выполнения.
Вывод показывает проблему: переменная с аннотацией number хранит строку, а последняя строка выбрасывает Cannot read properties of undefined (reading 'city'). Кроме того, any распространяется. user.name имеет тип any, поэтому любое значение, вычисленное из него, тоже any, и одно нетипизированное значение может отключить проверку далеко от того места, где оно появилось.
unknown заставляет сначала проверить
С unknown компилятор отвергает любую операцию, пока код не сузит значение. Сужение делается обычными проверками JavaScript, и внутри каждой ветки значение имеет проверенный тип.
Проверка value === null должна идти перед проверкой на объект, потому что typeof null это "object". После "id" in value TypeScript знает, что у объекта есть свойство id с типом unknown, ведь ничто не говорит, что в нём хранится. String() явно превращает его в текст; чтобы использовать его как число, сначала проверьте его через typeof.
Проверку можно пропустить и с помощью утверждения value as string, и компилятор его примет. Это обещание без какой-либо проверки во время выполнения, поэтому предпочитайте настоящую проверку; см. утверждения типов.
Проверка JSON через unknown
JSON.parse объявлен как возвращающий any, поэтому его результат молча отключает проверку. Аннотируйте результат как unknown и напишите защитник типа, который проверяет структуру, прежде чем остальной код станет ей доверять.
Первый ввод печатает dark at 14px, второй отвергается, потому что в нём нет fontSize. С any второй ввод прошёл бы дальше как Settings с неопределённым fontSize. Для больших схем ту же работу делает библиотека валидации, которая ещё и выводит тип за вас.
noImplicitAny
any появляется не только тогда, когда его пишут. Параметр без аннотации и без контекста для вывода тоже был бы any. Параметр noImplicitAny, который включает strict, сообщает об этом как об ошибке:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
Исправление это аннотация: function double(x: number). Явная запись x: any тоже компилируется, и в этом весь смысл: any остаётся видимым в коде, его можно найти поиском и проверить на ревью.
Где any всё ещё пробирается
Даже при strict эти источники дают any, хотя самого слова в вашем коде нет:
| Источник | Что получается | Что делать |
|---|---|---|
JSON.parse(text) | any | аннотировать как unknown, затем проверить |
response.json() с типами браузера (DOM) | Promise<any> | то же, что с JSON.parse (собственные типы fetch в Node уже возвращают Promise<unknown>) |
| Пакет без определений типов | any для его импортов (с ошибкой, если не добавить объявление) | установить @types/... или написать .d.ts |
value as any | any | использовать защитник типа или точное утверждение |
catch (e) с выключенным useUnknownInCatchVariables | any | strict делает его unknown; так и оставьте |
Переменная catch при strict имеет тип unknown, потому что выбросить можно что угодно, а не только объекты Error. Сузьте её через e instanceof Error, прежде чем читать e.message.
Когда any допустим
any не запрещён, но каждое его использование это место, которое компилятор больше не защищает. Разумные случаи:
- Миграция кодовой базы на JavaScript, где
anyотмечает то, что ещё не типизировано. - Код, который система типов не может хорошо выразить, небольшой и скрытый за типизированной сигнатурой функции.
- Тестовый код, который намеренно передаёт неверные данные.
Для всего остального unknown покрывает тот же случай «я не знаю этот тип», сохраняя проверки. Многие команды закрепляют это правилом линтера @typescript-eslint/no-explicit-any. Родственный тип Record<string, unknown> это обычный выбор для «какого-то объекта с неизвестными значениями».
Часто задаваемые вопросы
В чём разница между any и unknown в TypeScript?
Оба принимают любое значение. С any со значением можно делать что угодно (читать свойства, вызывать его, присваивать в number), и компилятор ничего не проверяет. С unknown почти ничего нельзя делать, пока вы не сузите тип проверкой вроде typeof x === "string". unknown сохраняет проверку типов, поэтому он безопаснее.
Когда использовать unknown вместо any?
Всегда, когда тип значения неизвестен при компиляции: разобранный JSON, данные из сетевого ответа, пойманная ошибка, вход функции валидации. Типизируйте такое значение как unknown и сужайте его. К any прибегайте только для временной работы по миграции или для кода, который система типов не может описать.
Что означает "Object is of type 'unknown'"?
Ошибки TS18046 ('x' is of type 'unknown') и TS2571 (Object is of type 'unknown', когда значение не просто имя, например load().id) означают, что вы использовали значение unknown так, будто у него есть конкретный тип, например прочитали свойство или вызвали метод. Сначала проверьте тип (typeof, instanceof, Array.isArray, in или функция защитника типа) и используйте значение внутри суженной ветки.
Почему JSON.parse возвращает any?
В стандартной библиотеке он объявлен как parse(text: string, ...): any, потому что компилятор не может знать, что содержит строка. Результат молча отключает проверку для всего, к чему прикасается. Вместо этого аннотируйте его: const data: unknown = JSON.parse(text), а затем проверьте перед использованием.
Что такое noImplicitAny?
Параметр компилятора, входящий в strict, который сообщает об ошибке, когда объявление молча получило бы тип any, потому что у него нет аннотации и тип не из чего вывести: TS7006 для параметра, TS7005 или TS7034 для переменной, тип которой нельзя определить. Он не даёт any появляться без того, чтобы кто-то его написал. Явный any по-прежнему разрешён.