any, unknown, never, void

Четыре специальных типа TypeScript: any отключает проверку, unknown требует проверки перед использованием, never означает «никогда», void — «ничего не возвращает».

Зачем нужно

  • Эти типы покрывают пограничные случаи системы типов
  • Понимание различий между ними — ключ к написанию безопасного кода
  • unknown — безопасная альтернатива any
  • never необходим для exhaustive checks и невозможных состояний

Где используется

  • any — миграция JS → TS, работа с нетипизированными библиотеками
  • unknown — обработка внешних данных (API, пользовательский ввод, JSON.parse)
  • never — exhaustive checks, функции с throw, невозможные ветки
  • void — функции без возвращаемого значения, callback-и

Предпосылки

any — «мне всё равно»

any полностью отключает проверку типов. Значение any можно использовать как угодно:

let value: any = 42;
value = "hello";          // OK
value = true;             // OK
value = { foo: "bar" };   // OK

// Можно вызывать любые методы — TS не проверяет
value.nonExistentMethod;  // OK при компиляции, упадёт в рантайме!
value.foo.bar.baz;          // OK при компиляции
value;                    // OK при компиляции

// any «заражает» другие типы
let num: number = value;    // OK — any совместим со всем
let str: string = value;    // OK

Когда any допустим

// 1. Миграция JS → TS (временно)
function legacyFunction(data: any): any {
  // TODO: типизировать позже
  return data.process;
}

// 2. Сторонняя библиотека без типов (временно)
declare const thirdPartyLib: any;

// 3. Намеренное отключение проверки (редко)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function debug(value: any): void {
  console.log(JSON.stringify(value, null, 2));
}

Неявный any

// С noImplicitAny: true (рекомендуется!)
function process(data) {
  //              ^^^^
  // Error: Parameter 'data' implicitly has an 'any' type
  return data.name;
}

// Нужно указать тип явно
function process(data: { name: string }): string {
  return data.name;
}

unknown — «не знаю, но проверю»

unknown — безопасная альтернатива any. Значение unknown нельзя использовать без проверки типа:

let value: unknown = 42;
value = "hello";          // OK — присвоить можно что угодно
value = true;             // OK

// НО использовать напрямую — нельзя!
value.toString();          // Ошибка!
value + 1;                 // Ошибка!
value.foo;                 // Ошибка!

let num: number = value;   // Ошибка! unknown не присваивается к конкретному типу

// Только к any или unknown
let a: any = value;        // OK
let u: unknown = value;    // OK

Type narrowing для unknown

function processValue(value: unknown): string {
  // typeof guard
  if (typeof value === "string") {
    return value.toUpperCase(); // OK — TS знает что это string
  }

  // instanceof guard
  if (value instanceof Date) {
    return value.toISOString(); // OK — TS знает что это Date
  }

  // Truthiness + typeof
  if (typeof value === "number") {
    return value.toFixed(2); // OK
  }

  // Type assertion (менее безопасно)
  return String(value);
}

unknown для внешних данных

// API response
async function fetchUser(id: number): Promise<unknown> {
  const response = await fetch(`/api/users/${id}`);
  return response.json(); // JSON.parse возвращает any, но мы оборачиваем в unknown
}

// Валидация данных
interface User {
  id: number;
  name: string;
  email: string;
}

function isUser(data: unknown): data is User {
  return (
    typeof data === "object" &&
    data !== null &&
    "id" in data &&
    "name" in data &&
    "email" in data &&
    typeof (data as User).id === "number" &&
    typeof (data as User).name === "string" &&
    typeof (data as User).email === "string"
  );
}

async function getUser(id: number): Promise<User> {
  const data = await fetchUser(id);
  if (isUser(data)) {
    return data; // Типобезопасно!
  }
  throw new Error("Invalid user data");
}

never — «этого никогда не будет»

never — тип для значений, которые никогда не возникнут. Это «нижний тип» — подтип всех типов.

// 1. Функция, которая никогда не завершится
function throwError(message: string): never {
  throw new Error(message);
}

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

// 2. Exhaustive check — проверка что все варианты обработаны
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 и забудет case:
      const _exhaustive: never = color;
      // Error: Type '"yellow"' is not assignable to type 'never'
      return _exhaustive;
  }
}

// 3. Невозможное пересечение типов
type Impossible = string & number; // never — строка и число одновременно невозможна

// 4. Пустой union после фильтрации
type NonString = Exclude<string | number | boolean, string>;
// number | boolean
type Nothing = Exclude<string, string>;
// never

Свойства never

// never — подтип любого типа
let x: string = throwError("!"); // OK — never совместим с любым типом
let y: number = throwError("!"); // OK

// Ничто не является подтипом never (кроме never)
let n: never = 42;       // Ошибка!
let n2: never = "hello"; // Ошибка!

// never в union «исчезает»
type A = string | never; // string
type B = number | never; // number

// never в intersection «поглощает»
type C = string & never; // never

void — «ничего не возвращаю»

void указывает что функция не возвращает значение:

// Явный void
function logMessage(msg: string): void {
  console.log(msg);
  // return; — OK
  // return undefined; — OK
  // return "value"; — Ошибка!
}

// Вывод типов — TS сам определит void
function logMessage(msg: string) {
  console.log(msg);
}
// Тип: (msg: string) => void

void в callback-ах — важный нюанс

// void в callback означает "возвращаемое значение ИГНОРИРУЕТСЯ"
type VoidCallback = () => void;

// Callback может возвращать что угодно — значение будет проигнорировано
const cb: VoidCallback = () => {
  return 42; // OK! Не ошибка, хотя тип void
};

const result = cb;
// result: void — нельзя использовать как number

// Реальный пример:
const numbers: number = ;
[1, 2, 3].forEach((n) => numbers.push(n));
// push возвращает number, но forEach ожидает void callback — это OK

Сравнительная таблица

Свойство any unknown never void
Присвоить что угодно Да Да Нет Только undefined
Использовать без проверки Да Нет
Присвоить другому типу Да Нет (только any/unknown) Да (любому) Нет
Назначение Отключить проверку Безопасный «не знаю» Невозможное значение Нет return
Безопасность Нулевая Высокая Максимальная Средняя
Рекомендуется Нет Да По ситуации Для функций

Иерархия типов

           unknown         ← top type (содержит всё)
          /   |    \
     string number boolean ... object
          \   |    /
           never            ← bottom type (подтип всего)
  • unknown — супертип всех типов (всё присваивается к unknown)
  • never — подтип всех типов (never присваивается к чему угодно)
  • any — «жульничество» — обходит систему типов в обе стороны

Частые ошибки

  1. Использовать any вместо unknown — теряется вся безопасность типов
  2. Путать void и undefinedvoid для функций, undefined для значений
  3. Не проверять unknown перед использованием — нужен type guard или assertion
  4. Забывать про exhaustive check с never — пропуск нового варианта enum/union
  5. Думать что void = undefined — в callback void разрешает возвращать любое значение
// Частая ошибка — any «заражает» код
function getConfig: any {
  return JSON.parse('{"port": 3000}');
}

const port = getConfig.port; // port: any — проверки нет!
port.nonExistent.method;      // Компилируется, падает в рантайме

// Правильно — через unknown
function getConfig: unknown {
  return JSON.parse('{"port": 3000}');
}

const config = getConfig;
// config.port; // Ошибка! Нужна проверка
if (typeof config === "object" && config !== null && "port" in config) {
  console.log((config as { port: number }).port); // OK
}

Практика

  1. Замените any на unknown в функции и добавьте type guards
  2. Напишите функцию assertNever(value: never): never для exhaustive checks
  3. Создайте union type и switch — проверьте что never ловит пропущенные варианты
  4. Используйте unknown для обработки JSON.parse результата с валидацией
  5. Поэкспериментируйте с void callback — верните число из callback с типом => void

Связанные темы

Ресурсы