Ніколи не вводьте в TypeScript
never - це нижній тип (bottom type) у TypeScript: значення цього типу не може існувати під час виконання програми.
Теорія
TL;DR
neverозначає "цей шлях у коді неможливий" - компілятор вважає його глухим кутом- Аналогія: дорога з знаком "немає виїзду". Жодне значення не виходить з іншого боку.
void= функція завершилась, але нічого не повернула.never= функція взагалі не завершується.const x: never = valueу гілціdefaultswitch - спіймає необроблені члени union на етапі компіляціїneverзникає в union-типах:string | never=string
Швидкий приклад
// Кидає виняток замість повернення - анотується як never
function fail(message: string): never {
throw new Error(message);
}
// Перевірка на вичерпність discriminated union
type Status = 'ok' | 'error' | 'pending';
function describe(s: Status): string {
switch (s) {
case 'ok': return 'Все добре';
case 'error': return 'Щось пішло не так';
case 'pending': return 'Ще чекаємо';
default:
const check: never = s; // помилка компіляції, якщо Status отримає новий член
throw new Error('Необроблений: ' + check);
}
}Додай новий член до Status, не оновивши switch, і компілятор видасть помилку саме на рядку з check. Баг знаходиться до того, як код потрапить у продакшен.
Головна різниця з void
void каже: функція дійшла до кінця і нічого корисного не повернула. Повернути undefined - цілком нормально. never каже: функція взагалі не досягне кінця - вона кинула виняток, зациклилась назавжди або пішла шляхом, який компілятор довів як недосяжний. З гілки never не виходить жодне значення.
Коли використовувати
- Допоміжні функції, що кидають помилку:
function fail(msg: string): never { throw new Error(msg); } - Вичерпна перевірка switch на discriminated union:
const x: never = valueу гілціdefault - Нескінченні цикли, які дійсно не повертаються: воркери, polling-цикли
- Фільтрація типів у conditional types:
T extends never ? ... : ...прибирає неможливі типи
Не варто використовувати never просто для позначення "порожнього" або "нічого". Для цього є void або undefined.
Як це обробляє компілятор
TypeScript під час аналізу потоку керування (control flow analysis) відстежує кожен досяжний шлях у коді. Після throw, після нескінченного циклу, або після того як усі члени union звужені - TypeScript позначає решту коду як never. Це мітка виключно на рівні компіляції. Node.js і V8 її не бачать: тип стирається разом з усіма іншими анотаціями до запуску.
Коли починаєш працювати з великою кодовою базою, де union-типи регулярно змінюються, exhaustive-перевірка через never стає найнадійнішим способом знаходити кожен switch, який потребує оновлення.
Типові помилки
Помилка 1: анотування порожнього масиву як never[]
const items: never[] = []; // неправильно - сюди не можна нічого додати
const items: string[] = []; // правильноМасив типу never[] відхиляє будь-який push і ініціалізацію зі значенням. TypeScript відмовить у будь-якому елементі, бо тип елемента є неможливим.
Помилка 2: спроба повернути значення з never-функції
function crash(): never {
throw new Error('down');
return 'oops'; // помилка компіляції - недосяжний код
}Кожен шлях у функції з анотацією never повинен кидати виняток або циклюватися. Оператор return порушує цей контракт.
Помилка 3: never у union в очікуванні якогось ефекту
type ID = string | number | never; // те саме що: string | numbernever поглинається будь-яким union-типом. Щоб прибрати конкретний член, використовуй Exclude<T, U>.
Помилка 4: never замість void для функцій без повернення
function log(msg: string): never { // помилка - функція завершується нормально
console.log(msg);
}
function log(msg: string): void { // правильно
console.log(msg);
}Функція, яка просто логує і завершується, є void, не never.
Де зустрічається в реальних проектах
- React-компоненти: перевірка на вичерпність у switch по variant пропсах -
const check: never = variantуdefault - Zod: при невдалому парсингу схема викликає функцію з типом
never, щоб сигналізувати зупинку виконання - Redux Toolkit:
PayloadAction<never>для actions без payload - tRPC: помилки процедур типізуються як
never, щоб TypeScript знав: результат не буде доступний
Питання на співбесіді
Q: Яка різниця між never і void?
A: void дозволяє undefined і означає функцію, яка нормально завершилась. never означає, що функція взагалі не досягне кінця. Жодне значення, навіть undefined, не можна присвоїти never.
Q: Коли TypeScript сам виводить never?
A: Після throw, після нескінченного циклу і після того як усі члени union звужені. Коли нічого не залишилось - тип стає never.
Q: Що відбувається з never у union-типі?
A: Він зникає. string | never - це просто string. Писати never у звичайному union не має жодного ефекту.
Q: Як працює exhaustive-перевірка через never?
A: Присвоюєш значення у гілці default змінній типу never. Якщо додаєш новий член union і не обробляєш його, TypeScript бачить, що змінна більше не є never, і видає помилку компіляції.
Q: Що таке asserts never і де воно зустрічається?
A: Це предикат типу на assertion-функціях. Функція з типом (x: unknown): asserts x is never повідомляє TypeScript: після її виклику значення, що передавалось, вважається неможливим і виключається з подальших перевірок.
Приклади
Вичерпна перевірка варіантів у компоненті
type AlertVariant = 'success' | 'error' | 'warning';
function getAlertClass(variant: AlertVariant): string {
switch (variant) {
case 'success': return 'bg-green-500';
case 'error': return 'bg-red-500';
case 'warning': return 'bg-yellow-500';
default:
// Додай 'info' до AlertVariant без оновлення цього switch -
// і цей рядок одразу дасть помилку компіляції
const exhausted: never = variant;
throw new Error(`Необроблений варіант: ${exhausted}`);
}
}Коли колега додає 'info' до AlertVariant і забуває оновити функцію, компілятор вказує на гілку default одразу. Баг з'являється під час компіляції, а не в браузері користувача.
Функція-помічник з типом never як будівельний блок
function failWith(message: string): never {
throw new Error(message);
}
function assertDefined<T>(value: T | null | undefined, field: string): T {
if (value == null) {
throw new Error(`${field} є обов'язковим`);
}
return value;
}
// Завдяки never TypeScript звужує token до string
const token = user.token ?? failWith('Відсутній токен');
// ^? string (не string | undefined)Тип never на failWith каже TypeScript: права частина ?? завжди зупиняє виконання. Тому token виводиться як string, а не string | undefined. Без цієї анотації звуження не відбудеться.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.