any, unknown, never, void
Четыре специальных типа TypeScript: any отключает проверку, unknown требует проверки перед использованием, never означает «никогда», void — «ничего не возвращает».
Зачем нужно
- Эти типы покрывают пограничные случаи системы типов
- Понимание различий между ними — ключ к написанию безопасного кода
unknown— безопасная альтернативаanyneverнеобходим для exhaustive checks и невозможных состояний
Где используется
any— миграция JS → TS, работа с нетипизированными библиотекамиunknown— обработка внешних данных (API, пользовательский ввод, JSON.parse)never— exhaustive checks, функции с throw, невозможные веткиvoid— функции без возвращаемого значения, callback-и
Предпосылки
- Примитивные типы — базовые типы
- Type guards — сужение типов (для работы с unknown)
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— «жульничество» — обходит систему типов в обе стороны
Частые ошибки
- Использовать
anyвместоunknown— теряется вся безопасность типов - Путать
voidиundefined—voidдля функций,undefinedдля значений - Не проверять
unknownперед использованием — нужен type guard или assertion - Забывать про exhaustive check с
never— пропуск нового варианта enum/union - Думать что
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
}
Практика
- Замените
anyнаunknownв функции и добавьте type guards - Напишите функцию
assertNever(value: never): neverдля exhaustive checks - Создайте union type и switch — проверьте что
neverловит пропущенные варианты - Используйте
unknownдля обработкиJSON.parseрезультата с валидацией - Поэкспериментируйте с void callback — верните число из callback с типом
=> void
Связанные темы
- Примитивные типы — базовые типы
- Type guards — сужение типов
- Union и Intersection — комбинирование типов
- Conditional types — условные типы с never