В чём отличие any, unknown, never?

MiddleTypeScript #typescript

Вопрос

Чем отличаются типы any, unknown и never в TypeScript?

Короткий ответ

any полностью отключает проверку типов для значения. unknown — тоже "любое значение", но безопасный: перед использованием его нужно сузить (type narrowing). never — тип значений, которых не существует, обычно означает недостижимый код или функцию, которая никогда не возвращает управление.

Подробный ответ

  • any — "выход" из системы типов. С переменной типа any можно делать что угодно без ошибок компилятора: вызывать как функцию, обращаться к любым свойствам, присваивать в переменную любого другого типа. Это удобно, но лишает TypeScript смысла — ошибки всплывут только в рантайме.
  • unknown — типобезопасная альтернатива any. В переменную типа unknown можно записать что угодно, но использовать её нельзя, пока TypeScript не убедится в конкретном типе — через typeof, instanceof, пользовательский type guard или явное приведение (as). Это заставляет разработчика явно обработать все возможные варианты.
  • never — тип, у которого нет ни одного возможного значения. Используется для:
    • функций, которые всегда бросают исключение или уходят в бесконечный цикл (не возвращают управление);
    • результата исчерпывающей проверки switch/if по объединению типов (exhaustiveness check) — в ветке "не должно случиться" тип сужается до never.

Пример

let a: any = 'hello'
a.toFixed() // компилируется без ошибки, упадёт в рантайме

let u: unknown = 'hello'
u.toUpperCase() // Error: Object is of type 'unknown'
if (typeof u === 'string') {
  u.toUpperCase() // OK — тип сужен до string
}

function fail(message: string): never {
  throw new Error(message)
}

type Shape = { kind: 'circle'; r: number } | { kind: 'square'; side: number }

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.r ** 2
    case 'square': return shape.side ** 2
    default:
      const exhaustiveCheck: never = shape // ошибка компиляции, если забыт кейс
      return exhaustiveCheck
  }
}

Дополнительные вопросы

  • Почему unknown считается более безопасной заменой any?
  • Как never помогает поймать забытый case в switch на этапе компиляции?
  • Может ли переменная реально когда-либо иметь значение типа never?