ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

Any, unknown и never: неизвестные значения и невозможные состояния

Автор: · Обновлено

any, unknown и never полезно различать по тому, какие действия они разрешают. Первый отключает существенную часть проверки, второй требует доказать возможность операции, а третий…

any, unknown и never полезно различать по тому, какие действия они разрешают. Первый отключает существенную часть проверки, второй требует доказать возможность операции, а третий обозначает отсутствие допустимого значения в рассматриваемой точке программы.

Цель и исходные знания

Вы уже знаете аннотации параметров и простые объектные типы. После урока сможете выбрать тип для непроверенного входа, не распространять any по приложению и объяснить роль never без метафоры «странное пустое значение». Примеры выполняются независимо друг от друга в TypeScript 6 с strict; намеренно неправильные строки находятся в невыполняемой ветке.

Один вход, две степени доверия

Представьте импорт длительности занятия из неизвестной системы. Иногда приходят минуты числом, иногда — строка с комментарием. Функция расчёта не должна делать вид, что оба значения одинаково пригодны для умножения. Нужна граница: до неё данные ещё неизвестны, после неё получено конкретное число с проверенными ограничениями. Тип unknown помогает удержать эту границу видимой для компилятора.

export {};

function requireMinutes(value: unknown): number {
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
    throw new Error('Ожидалось конечное неотрицательное число минут');
  }
  return value;
}

const unchecked: any = { minutes: 'скоро' };
console.log(unchecked.minutes * 2);
console.log(requireMinutes(45) * 2);
try {
  requireMinutes('45');
} catch (error: unknown) {
  console.log(error instanceof Error ? error.message : 'Неизвестная ошибка');
}

if (false) {
  const pending: unknown = '45';
  // @ts-expect-error Нельзя вычислять с неизвестным типом без проверки.
  console.log(pending * 2);
}

Будут напечатаны NaN, 90 и сообщение о неверном числе минут. Первая операция прошла проверку типов только потому, что any разрешил её без достаточной информации. Вторая использовала результат проверяющей функции. Третья намеренно не преобразует строку в число: допустимость такого преобразования должна определяться форматом импорта, а не случайным поведением оператора умножения.

Проверка typeof value === 'number' сама по себе недостаточна для выбранного правила: NaN и бесконечность также относятся к number. Дополнительные условия описывают конкретную задачу. Если для другого продукта отрицательные числа имеют смысл, менять следует правило, а не выбирать более хитрый TypeScript-тип в надежде автоматически исправить данные.

Как any распространяет неопределённость

Из значения any легко получить другое any: прочитать свойство, вызвать метод, использовать возвращённый результат. После нескольких таких переходов неизвестно уже не только содержимое входа, но и корректность значительной части вычисления. Особенно неприятно, когда небезопасное значение выходит из маленькой вспомогательной функции с неявно выведенным результатом. Тогда потребители теряют проверки, хотя сами нигде не написали any.

Это не означает, что any запрещён как часть языка. Он встречается при постепенном переводе старого кода, в низкоуровневых адаптерах и некоторых типовых конструкциях библиотек. Практический вопрос — насколько мала область доверия и чем она заканчивается. На границе системы часто лучше сразу присвоить результат переменной unknown, проверить его и вернуть понятную модель. Нельзя считать unknown валидатором: он заставляет разработчика написать нужную проверку, но не выполняет её самостоятельно.

Где появляется never

Функция с результатом never не возвращает управление обычным способом: например, она всегда выбрасывает исключение. Это отличается от void, который говорит вызывающей стороне не рассчитывать на полезное возвращаемое значение. Другая важная ситуация — ветка после обработки всех вариантов объединения. Если ни одного допустимого случая не осталось, компилятор может представить оставшееся значение как never.

export {};

type Delivery = { kind: 'email'; address: string } | { kind: 'pickup'; desk: number };
function impossible(value: never): never {
  throw new Error('Необработанный вариант: ' + JSON.stringify(value));
}
function destination(delivery: Delivery): string {
  switch (delivery.kind) {
    case 'email': return delivery.address;
    case 'pickup': return 'Стойка ' + delivery.desk;
    default: return impossible(delivery);
  }
}
console.log(destination({ kind: 'pickup', desk: 4 }));

Вывод — Стойка 4. Добавьте в Delivery третий вариант и не меняйте функцию: аргумент в impossible(delivery) перестанет соответствовать never. Ошибка показывает конкретное незавершённое изменение. Здесь не нужно создавать значение типа never; смысл проверки в том, чтобы доказать отсутствие оставшихся разрешённых вариантов.

Ошибки, которые скрываются за красивыми словами

Не присваивайте проверенному типу неизвестный вход через двойное утверждение as unknown as ...: такая цепочка не является двумя проверками. Не возвращайте never из функции, которая иногда доходит до конца. Не путайте «тип не содержит нормальных значений» с «в JavaScript есть специальное значение never». Типы существуют для анализа программы, а выполняемый код по-прежнему работает с обычными значениями и исключениями.

Практикум: импорт ограниченного количества мест

Напишите функцию requireCapacity(value: unknown): number. Допустимы только целые числа от одного до тридцати включительно. Проверьте 12, 0, 31, 2.5, '12', null и NaN. Затем оберните вызов в обработчик ошибки, который формирует сообщение для пользователя, не обращаясь к error.message до проверки формы ошибки.

Разбор: сначала нужен typeof, затем Number.isInteger, затем границы диапазона. Порядок делает каждую следующую операцию обоснованной. Строку '12' не принимают, поскольку условие задания не разрешает преобразование; если оно понадобится, это будет отдельная стадия импорта с собственными примерами. Обработчик использует error instanceof Error, а для другого брошенного значения даёт запасное сообщение. Полезный результат практики — таблица входов и ожидаемых исходов, а не только отсутствие красного подчёркивания в редакторе.

Частые вопросы

Чем [never] отличается от never?

[never] — тип кортежа длины один, чей обязательный элемент имеет тип never; это не сам never и не пустой кортеж []. Обычное допустимое значение элемента создать нельзя, но форма кортежа остаётся значимой для типовых операций. В условных типах обёртка квадратными скобками также используется для управления распределением по union.

Почему нельзя просто заменить любой any на unknown?

После механической замены часто обнаруживаются реальные неописанные предположения: какие свойства обязательны, какие значения допустимы, откуда они пришли. Их нужно разрешить проверками или корректными сигнатурами. Замена полезна как начало анализа, но исправление задачи состоит в восстановлении границ доверия, а не в выборе другого слова.

Источники