Перейти к содержимому
Теория · Асинхронный код и данные извне · урок 9 из 10

Утверждения as и !

На таможне декларацию к посылке пишет отправитель. Её можно и подделать: написать «книги» на коробке, в которой лежит что угодно. Таможенник сверяет бумаги, а не содержимое, поэтому поверит.

В TypeScript такая подделка — запись as. Она говорит tsc: «считай, что у этого значения вот такой тип». Проверять значение tsc не станет:

TypeScript
const price: unknown = "100";
const amount = price as number;
console.log(amount.toFixed(2)); // ✗
TEXT
TypeError: amount.toFixed is not a function

tsc молчит: после 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; он не пропустит:

TEXT
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». Он убирает их из типа и тоже ничего не проверяет при запуске:

TypeScript
const prices = new Map<string, number>();
prices.set("tea", 120);
const tea = prices.get("tea")!;
console.log(tea.toFixed(2)); // 120.00

Разберём по строкам:

  1. prices.get("tea") возвращает number | undefined: такого ключа может не оказаться.
  2. ! убирает undefined, и у tea тип number.
  3. Ключ "tea" в prices есть, поэтому программа печатает 120.00.

Без ! строка tea.toFixed(2) не прошла бы проверку: tsc сообщил бы TS18048: 'tea' is possibly 'undefined'.

satisfies проверяет и не меняет тип

У as есть честный сосед — satisfies. Он сверяет значение с типом, но тип самого значения оставляет прежним:

TypeScript
type Config = { port: number | string };

const config = { port: 8080 } satisfies Config;
console.log(config.port.toFixed(0)); // 8080

tsc проверил, что { 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, и готовы за это отвечать.

Частая ошибка

! поставили там, где значения нет:

TypeScript
const prices = new Map<string, number>();
prices.set("tea", 120);
const coffee = prices.get("coffee")!; // ✗
console.log(coffee.toFixed(2));
TEXT
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 проверяет значение по типу и оставляет выведенный тип.