Утверждения as и !
На таможне декларацию к посылке пишет отправитель. Её можно и подделать: написать «книги» на коробке, в которой лежит что угодно. Таможенник сверяет бумаги, а не содержимое, поэтому поверит.
В TypeScript такая подделка — запись as. Она говорит tsc: «считай, что у этого значения вот такой тип». Проверять значение tsc не станет:
const price: unknown = "100";
const amount = price as number;
console.log(amount.toFixed(2)); // ✗TypeError: amount.toFixed is not a functiontsc молчит: после as number он считает amount числом. Но в переменной по-прежнему строка "100", а у строки нет метода toFixed, и программа падает при запуске.
Утверждение типа
Запись значение as Тип — утверждение типа. Оно меняет только то, во что верит tsc, и ничего не делает при запуске:
- значение не превращается в другой тип: строка остаётся строкой;
- проверки при запуске нет: в JavaScript запись
const amount = price as number;становитсяconst amount = price;; - ошибку
tscне находит, и она всплывает при запуске, часто далеко отas.
Это не приведение типа вроде Number(price): as ничего не преобразует. Его пишут, только когда вы точно знаете о значении больше, чем tsc. Для данных извне вместо as нужна настоящая проверка: typeof или предикат типа.
Запись as const тоже называют утверждением, в сообщениях tsc — const assertion. Но подделать с ней ничего нельзя: она не сообщает tsc ничего нового о значении, а лишь просит не расширять типы литерала.
Когда as не пропускают
Совсем без проверки as всё же не обходится. Если у двух типов нет ничего общего, tsc откажет. Например, запись const n = "42" as number; он не пропустит:
error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.Сообщение подсказывает обход: сначала утвердить тип unknown. Запись "42" as unknown as number компилируется, но это подделка поверх подделки: в n всё равно строка. Из unknown и any утверждение пропускает любой тип, поэтому первый пример урока и прошёл проверку.
Оператор !
Знак ! после выражения — утверждение особого вида: «здесь не null и не undefined». Он убирает их из типа и тоже ничего не проверяет при запуске:
const prices = new Map<string, number>();
prices.set("tea", 120);
const tea = prices.get("tea")!;
console.log(tea.toFixed(2)); // 120.00Разберём по строкам:
prices.get("tea")возвращаетnumber | undefined: такого ключа может не оказаться.!убираетundefined, и уteaтипnumber.- Ключ
"tea"вpricesесть, поэтому программа печатает120.00.
Без ! строка tea.toFixed(2) не прошла бы проверку: tsc сообщил бы TS18048: 'tea' is possibly 'undefined'.
satisfies проверяет и не меняет тип
У as есть честный сосед — satisfies. Он сверяет значение с типом, но тип самого значения оставляет прежним:
type Config = { port: number | string };
const config = { port: 8080 } satisfies Config;
console.log(config.port.toFixed(0)); // 8080tsc проверил, что { port: 8080 } подходит к Config: запись { port: true } satisfies Config дала бы ошибку. При этом у config.port остался выведенный тип number, поэтому toFixed проходит. Сравним три записи:
- аннотация
const config: Configпроверяет значение и меняет тип наConfig, и тогда уconfig.portтипstring | number; satisfies Configпроверяет значение и оставляет выведенный тип;as Configпочти ничего не проверяет и меняет тип.
На собеседовании часто спрашивают, что as делает при запуске и чем он отличается от satisfies. Ответ: при запуске as не делает ничего, он стирается вместе с типами, а satisfies проверяет значение по типу, не меняя его выведенный тип.
Здесь аналогия с подделкой хромает в одном. Подделывать декларации незаконно, а as — законный инструмент. Им пользуются, когда вы знаете о значении больше, чем tsc, и готовы за это отвечать.
Частая ошибка
! поставили там, где значения нет:
const prices = new Map<string, number>();
prices.set("tea", 120);
const coffee = prices.get("coffee")!; // ✗
console.log(coffee.toFixed(2));TypeError: Cannot read properties of undefined (reading 'toFixed')Ключа "coffee" в prices нет, get вернул undefined, а ! убедил tsc в обратном. tsc молчит, и программа падает на следующей строке. Вместо ! проверьте значение: if (coffee !== undefined).
Главное
asменяет только то, во что веритtsc: при запуске он стирается и ничего не преобразует.- Между несвязанными типами
asдаётTS2352, аas unknown asобходит проверку, не меняя значения. !убираетnullиundefinedиз типа и тоже ничего не проверяет при запуске.satisfiesпроверяет значение по типу и оставляет выведенный тип.