← Курс/any, unknown и never: особые типы#146 из 257+25 XP

any, unknown и never: особые типы

Тип any — отключение проверки типов

any — это «выход» из системы типов TypeScript. Переменная с типом any может быть чем угодно, и TypeScript перестаёт проверять операции с ней.

let value: any = 42
value = 'строка'        // OK
value = true            // OK
value = { name: 'test' } // OK

value.foo()             // OK для TS, но может упасть в runtime
value[0].bar.baz        // OK для TS — проверки отключены

Использование any — плохая практика. Оно полностью убивает цель TypeScript. Оправданных случаев немного: миграция старого JS-кода, работа с очень динамичными данными, быстрое прототипирование.

Тип unknown — безопасная альтернатива any

unknown — это «безопасный any». Переменная типа unknown тоже может содержать любое значение, но TypeScript **запрещает** с ней что-либо делать без предварительной проверки типа.

let value: unknown = 'hello'

// Нельзя напрямую использовать:
value.toUpperCase()     // Ошибка: Object is of type 'unknown'
value.length            // Ошибка: Object is of type 'unknown'

// Нужно сначала сузить тип (narrowing):
if (typeof value === 'string') {
  value.toUpperCase()   // OK — TypeScript знает что value: string
}

if (typeof value === 'number') {
  value.toFixed(2)      // OK — TypeScript знает что value: number
}

unknown идеален для данных из внешних источников: API-ответы, JSON.parse, данные из форм. Вы обязаны проверить тип перед использованием.

async function fetchData(): Promise<unknown> {
  const response = await fetch('/api/data')
  return response.json()  // json() возвращает any, но мы помечаем как unknown
}

const data = await fetchData()

// Обязательная проверка перед использованием:
if (typeof data === 'object' && data !== null && 'name' in data) {
  console.log((data as { name: string }).name)
}

Тип never — невозможный тип

never — тип для значений, которые никогда не существуют. Он используется в трёх основных сценариях:

1. Функции, которые никогда не возвращают:

function throwError(message: string): never {
  throw new Error(message)
  // Код после throw никогда не выполнится
}

function infiniteLoop(): never {
  while (true) { }
}

2. Исчерпывающие проверки (exhaustive checks):

type Color = 'red' | 'green' | 'blue'

function getHex(color: Color): string {
  switch (color) {
    case 'red':   return '#FF0000'
    case 'green': return '#00FF00'
    case 'blue':  return '#0000FF'
    default:
      // Если добавить 'yellow' в Color но забыть обработать здесь —
      // TypeScript выдаст ошибку: 'yellow' не assignable to never
      const exhaustiveCheck: never = color
      throw new Error(`Неизвестный цвет: ${exhaustiveCheck}`)
  }
}

3. Пустое объединение:

type Result = string & number  // never — строка не может быть числом одновременно

Сравнение трёх типов

| Тип | Присвоение | Использование | Когда применять |

|-----|-----------|---------------|-----------------|

| any | Любое значение | Без ограничений | Миграция JS-кода |

| unknown | Любое значение | После проверки типа | Внешние данные, API |

| never | Только never | Недостижимый код | Исчерпывающие проверки |

Примеры

Демонстрация концепций any, unknown и never через runtime-проверки в JavaScript

// === Концепция any: нет проверок ===
// В JS всё и так динамично, как any в TS
function processAny(value) {
  // В TS: let value: any — полная свобода, но и полная опасность
  try {
    return value.toUpperCase()
  } catch (e) {
    return `Ошибка: ${e.message}`
  }
}

console.log('=== any (без проверок) ===')
console.log(processAny('hello'))       // 'HELLO'
console.log(processAny(42))            // 'Ошибка: value.toUpperCase is not a function'

// === Концепция unknown: обязательная проверка перед использованием ===
function processUnknown(value) {
  // В TS: unknown требует сужения типа через typeof/instanceof
  if (typeof value === 'string') {
    return value.toUpperCase()
  }
  if (typeof value === 'number') {
    return value.toFixed(2)
  }
  if (Array.isArray(value)) {
    return `Массив из ${value.length} элементов`
  }
  return `Неизвестный тип: ${typeof value}`
}

console.log('\n=== unknown (безопасная обработка) ===')
console.log(processUnknown('hello'))     // 'HELLO'
console.log(processUnknown(3.14159))     // '3.14'
console.log(processUnknown([1, 2, 3]))   // 'Массив из 3 элементов'
console.log(processUnknown(true))        // 'Неизвестный тип: boolean'

// === Концепция never: исчерпывающие проверки ===
function getStatusMessage(status) {
  switch (status) {
    case 'loading': return 'Загрузка...'
    case 'success': return 'Готово!'
    case 'error':   return 'Ошибка!'
    default:
      // В TS: const check: never = status — TS выдаст ошибку если добавить новый статус
      throw new Error(`Необработанный статус: ${status}`)
  }
}

console.log('\n=== never (исчерпывающая проверка) ===')
console.log(getStatusMessage('loading'))  // 'Загрузка...'
console.log(getStatusMessage('success'))  // 'Готово!'
console.log(getStatusMessage('error'))    // 'Ошибка!'

try {
  getStatusMessage('pending')  // В TS это поймал бы компилятор через never
} catch (e) {
  console.log(e.message)       // 'Необработанный статус: pending'
}

// === Функция, которая никогда не возвращает (never) ===
function assertNever(message) {
  // В TS: возвращаемый тип never
  throw new Error(`Unreachable: ${message}`)
}

console.log('\n=== Функция никогда не возвращает ===')
try {
  assertNever('этот код не должен выполняться')
} catch (e) {
  console.log(e.message)  // 'Unreachable: этот код не должен выполняться'
}