Тип unknown
Посылка со штампом «не досматривать» проходит границу без проверки, и беда обнаруживается потом. У таможни есть и другой порядок. Коробку, о которой ничего не известно, пропускают в страну опечатанной. Её можно хранить на складе и передавать дальше, но пользоваться содержимым нельзя, пока коробку не вскроют и не проверят.
В TypeScript опечатанная коробка — тип unknown. Положить в него можно что угодно, а достать — только после проверки:
const value: unknown = JSON.parse('"hello"');
console.log(value.toUpperCase()); // ✗error TS18046: 'value' is of type 'unknown'.Сообщение говорит: у value тип unknown, и пока он такой, tsc не даст вызвать метод. Внутри на самом деле строка "hello", но компилятор этого не знает и не делает вид, что знает.
Положить можно что угодно
В переменную типа unknown подходит любое значение: число, строка, null, объект. Ограничения начинаются, когда значение хотят использовать:
let box: unknown = 5;
box = "five";
box = null;
const count: number = box; // ✗error TS2322: Type 'unknown' is not assignable to type 'number'.Разберём пример по строкам:
boxобъявлена с типомunknown, в ней число5.- Строку
"five"компилятор пропускает. nullтоже подходит.- А положить
boxв переменную типаnumberнельзя: компилятор не знает, число ли там сейчас.
С unknown запрещено всё, что any разрешает без вопросов:
- вызывать методы;
- читать свойства;
- класть значение в переменную конкретного типа, например
number.
Проверка открывает коробку
Вскрыть коробку помогает обычная проверка JavaScript — оператор typeof. tsc читает условие и внутри if считает значение строкой:
const value: unknown = JSON.parse('"hello"');
if (typeof value === "string") {
console.log(value.toUpperCase()); // HELLO
}Внутри if у value тип string, поэтому вызов toUpperCase() проходит проверку. Если бы JSON.parse вернул число, условие не выполнилось бы, и программа не упала бы на вызове.
Так же работают проверки других типов. После typeof value === "number" значение внутри if получает тип number, после typeof value === "boolean" — тип boolean. Проверка открывает коробку ровно для того типа, который вы назвали.
В виджете одно и то же значение проходит три этапа. Следите за подсказкой: с any вызов метода пропускают без вопросов, с unknown — запрещают, а после проверки typeof — снова разрешают.
1const raw = JSON.parse('"hello"');подсказка редактораraw: any2const a: any = raw;3console.log(a.toUpperCase());4const u: unknown = raw;5console.log(u.toUpperCase());6if (typeof u === "string") {7 console.log(u.toUpperCase());8}
JSON.parse возвращает any: он не знает, что лежит в строке.
Здесь аналогия с опечатанной коробкой перестаёт работать. Настоящую пломбу видно и в стране, а unknown существует только для tsc. В JavaScript-файле от него ничего не остаётся, и в переменной лежит обычная строка.
unknown или any
На собеседовании часто спрашивают: «Чем unknown отличается от any?» Ответ укладывается в три пункта:
- в оба типа можно положить любое значение;
anyразрешает со значением всё без проверки, и ошибки всплывают при запуске;unknownне разрешает ничего, пока значение не проверено, и ошибки ловитtsc.
Отсюда правило: если вы не знаете, что придёт, — например, результат JSON.parse, — берите unknown и проверяйте значение перед использованием.
Частая ошибка
Проверили, что значение есть, но не проверили, что это строка:
const value: unknown = JSON.parse('"hello"');
if (value) {
console.log(value.toUpperCase()); // ✗
}error TS2339: Property 'toUpperCase' does not exist on type '{}'.Условие if (value) отсекает только null, undefined и другие ложные значения. Внутри if компилятор знает лишь, что значение не null и не undefined: это и значит тип {}. Там может оказаться число или объект, поэтому метод строки вызвать нельзя. Нужна проверка typeof value === "string".
Главное
- В
unknownможно положить любое значение, но использовать его можно только после проверки. - Без проверки
tscсообщаетerror TS18046: 'value' is of type 'unknown'. - После проверки
typeof value === "string"значение внутриifимеет типstring. anyвыключает проверку, аunknownзаставляет проверить значение, поэтому данные извне читают какunknown.