Типові асерції в TypeScript
Типові асерції (type assertions) дозволяють перевизначити тип, який TypeScript вивів для значення, і сказати компілятору "я знаю, що тут". Значення не змінюється, перевірок під час виконання немає - це виключно підказка на етапі компіляції.
Теорія
TL;DR
- Асерція - це ніби кажеш компілятору "довір мені": він перестає скаржитися, але якщо тип не той, помилка прилетить під час виконання
- Два синтаксиси:
value as Type(працює скрізь) і<Type>value(не працює в JSX) - У скомпільованому JS асерції зникають повністю - жодного коду не генерується
- Використовуй, коли маєш зовнішні докази типу (документація, ручна перевірка); в інших випадках - type guards
as constіsatisfiesчасто кращий вибір, ніж проста асерція
Швидкий приклад
const value: any = "hello world";
// Асерція каже компілятору: вважай це рядком
const length = (value as string).length; // 11
// Неправильний тип все одно зкрашить - компілятор тебе не врятує
const wrong = ("hello" as unknown as number);
console.log(typeof wrong); // "string" - під час виконання все одно рядокJS-вивід для value as string - просто value. Асерція повністю зникає при компіляції.
Два синтаксиси
TypeScript підтримує два способи записати асерцію:
// Синтаксис as - працює скрізь, рекомендований
const input = document.getElementById('email') as HTMLInputElement;
// Синтаксис з кутовими дужками - не працює в JSX
const input2 = <HTMLInputElement>document.getElementById('email');У React TSX-файлах завжди використовуй as. Кутові дужки парсер сприймає як JSX-тег і видає помилку.
Головна різниця від приведення типів
Асерції існують лише на етапі компіляції. На відміну від Java або C#, де приведення реально конвертує значення, TypeScript просто прибирає асерцію при компіляції:
const value: any = "42";
const num = value as number; // Компілюється без помилок
console.log(num * 2); // NaN - під час виконання це все ще рядокЖодного коду не емітується, жодної перевірки не відбувається. Чиста підказка на етапі збірки.
Коли використовувати
- DOM-елементи з відомим ID:
document.getElementById('email') as HTMLInputElement- ти знаєш тип елемента, TypeScript ні - Результат JSON.parse: спочатку
unknown, потім перевірка структури, потім асерція до інтерфейсу - Сторонні бібліотеки з неточними типами: асерція після власної перевірки форми об'єкта
- Union-типи, які ти вже звузив вручну: асерція замість зайвого type guard для критичних за продуктивністю шляхів
Не використовуй асерцію для значень, тип яких ти не перевірив. Є сумніви - type guard.
Non-null асерція
Оператор ! - скорочена форма: "це точно не null і не undefined":
// TypeScript бачить: HTMLElement | null
const element = document.getElementById('root');
// Non-null асерція - ти впевнений, що елемент є
element!.innerHTML = 'Hello';Це найчастіше джерело runtime-крашів у TypeScript-проектах. Використовуй тільки коли дійсно впевнений у наявності значення, і надавай перевагу if-перевірці, коли є сумніви.
as const
as const відрізняється від звичайної асерції. Він фіксує значення в літеральному типі і робить все readonly:
// Без as const - TypeScript розширює до string[]
const colors = ['red', 'green', 'blue']; // string[]
// З as const - точний readonly tuple
const colorsConst = ['red', 'green', 'blue'] as const;
// readonly ['red', 'green', 'blue']
type Color = typeof colorsConst[number]; // 'red' | 'green' | 'blue'Цей патерн часто використовують для виведення union-типів з масивів констант.
satisfies vs асерція (TypeScript 4.9+)
satisfies перевіряє, що значення відповідає типу, але зберігає найвужчий виведений тип кожної властивості. Звичайна анотація або асерція розширює все:
type Colors = 'red' | 'green' | 'blue';
// З анотацією типу - конкретні типи губляться
const palette: Record<Colors, string | number[]> = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
};
palette.red; // string | number[] - втрата точності
// З satisfies - перевіряє сумісність, зберігає точні типи
const palette2 = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255]
} satisfies Record<Colors, string | number[]>;
palette2.red; // number[]
palette2.green; // stringНа TypeScript 4.9+ satisfies зазвичай кращий вибір для типізації об'єктів.
Подвійна асерція
Коли TypeScript блокує асерцію між несумісними типами, можна зробити два кроки через unknown:
const value: string = "hello";
// Помилка: типи string і number не перетинаються
// const num = value as number;
// Подвійна асерція - компілюється, але майже завжди неправильно
const num = value as unknown as number;Подвійна асерція - сигнал, що структура типів вимагає перегляду, а не черговий інструмент в арсеналі.
Типові помилки
Асерція без перевірки на null:
// Ти припускаєш, що елемент є - але він може бути відсутній
const input = document.getElementById('email') as HTMLInputElement;
input.value = 'test'; // Runtime crash, якщо елемента нема в DOM
// Краще
const input = document.getElementById('email');
if (input instanceof HTMLInputElement) {
input.value = 'test';
}Кутові дужки в TSX:
// Помилка парсера - React сприймає це як JSX-елемент
const el = <HTMLInputElement>document.getElementById('foo');
// Правильно
const el = document.getElementById('foo') as HTMLInputElement;Асерція до структурно неповного типу:
interface User { name: string; age: number; }
const obj = { name: 'Alice' } as User;
console.log(obj.age); // undefined - TypeScript це пропустивСтруктурна типізація дозволяє відсутні поля при асерції. Перевіряй структуру перед тим, як стверджувати тип.
Використання as any для приховування реальних помилок:
// Погано - всі перевірки вимкнено
function processData(data: ComplexType) {
return (data as any).someMethod();
}
// Краще - спочатку перевір
function processData(data: ComplexType) {
if ('someMethod' in data && typeof (data as any).someMethod === 'function') {
return (data as { someMethod: () => unknown }).someMethod();
}
}Де зустрічається в реальному коді
- React:
useRef<HTMLElement>(null)і потімref.current as HTMLElementв обробниках подій - Express:
req.body as { userId: number }після того, як middlewareexpress.json()вже перевірив структуру - Next.js: асерція результатів
getServerSidePropsдо форми пропсів сторінки - Redux Toolkit:
payload as SpecificPayloadвсередині обробників дій з discriminated union - JSON.parse: спочатку
unknown, потім перевірка черезin, потім асерція до інтерфейсу
Питання на співбесіді
Q: Яка різниця між as і синтаксисом з кутовими дужками?
A: Функціонально однакові. Але <T>expr не працює в JSX - парсер читає це як тег. Тому в React/TSX завжди as.
Q: Чи генерує асерція якийсь код під час виконання?
A: Ні. TypeScript повністю видаляє асерції при компіляції. JS-вивід ідентичний незалежно від наявності асерції.
Q: Коли використовувати type guard замість асерції?
A: Коли немає зовнішніх доказів типу. Type guard (typeof, instanceof, in) звужує тип через реальну перевірку під час виконання. Асерція пропускає цю перевірку і просто вірить тобі.
Q: Що таке as const і чим воно відрізняється від звичайної асерції?
A: Звичайна асерція звужує до названого типу. as const фіксує значення в точному літеральному типі і робить все readonly. Використовується для виведення union-типів з масивів або фіксації конфіг-об'єктів.
Q: Чому satisfies часто краще за асерцію для типізації об'єктів?
A: Тому що асерції губляють виведення типів. satisfies перевіряє відповідність об'єкта типу, але зберігає найвужчий виведений тип кожної властивості. const config = { timeout: 5000 } satisfies Config зберігає config.timeout як number, а не як Config. Анотація типу розширила б його.
Q: Є JSON.parse(rawString). Як безпечно типізувати результат?
A: Спочатку unknown, потім in-перевірки або type guard для валідації структури, і тільки потім асерція. Асерція прямо з JSON.parse без перевірки - це runtime-краш, що чекає свого часу: схема може не відповідати реальним даним.
Приклади
Базовий: виведення довжини рядка
const data: any = "TypeScript";
const strLength = (data as string).length;
console.log(strLength); // 10Ти кажеш компілятору, що data це рядок, тому .length доступний. Під час виконання data і справді рядок - крашу немає. Якби data було 42, отримав би undefined, бо у чисел немає .length.
Середній: DOM-input в обробнику форми
const handleSubmit = (e: Event) => {
e.preventDefault();
const emailInput = document.getElementById('email') as HTMLInputElement;
if (!emailInput) return; // спочатку перевірка на null
const email = emailInput.value;
console.log('Відправляємо:', email);
};
document.getElementById('form')?.addEventListener('submit', handleSubmit);getElementById повертає HTMLElement | null. Асерція до HTMLInputElement дає доступ до .value та інших властивостей, специфічних для input. Перевірка на null перед використанням запобігає краш у рантаймі.
Просунутий: satisfies vs асерція для конфіг-об'єктів
type Env = 'development' | 'production' | 'test';
interface AppConfig {
env: Env;
apiUrl: string;
timeout: number;
}
// З анотацією типу - точні літерали розширюються
const configA = {
env: 'production',
apiUrl: 'https://api.example.com',
timeout: 5000
} as AppConfig;
configA.env; // Env - розширено до повного union
// З satisfies - перевіряє структуру, зберігає літерали
const configB = {
env: 'production',
apiUrl: 'https://api.example.com',
timeout: 5000
} satisfies AppConfig;
configB.env; // 'production' - точний літерал збереженоЗустрічав кодові бази, де перехід з as AppConfig на satisfies AppConfig одразу виявив відсутні обов'язкові поля, які асерція мовчки пропускала. У конфіг-файлах продакшн-проектів satisfies дає одночасно і перевірку типу, і точне виведення.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.