Skip to main content

Типові асерції в TypeScript

Типові асерції (type assertions) дозволяють перевизначити тип, який TypeScript вивів для значення, і сказати компілятору "я знаю, що тут". Значення не змінюється, перевірок під час виконання немає - це виключно підказка на етапі компіляції.

Теорія

TL;DR

  • Асерція - це ніби кажеш компілятору "довір мені": він перестає скаржитися, але якщо тип не той, помилка прилетить під час виконання
  • Два синтаксиси: value as Type (працює скрізь) і <Type>value (не працює в JSX)
  • У скомпільованому JS асерції зникають повністю - жодного коду не генерується
  • Використовуй, коли маєш зовнішні докази типу (документація, ручна перевірка); в інших випадках - type guards
  • as const і satisfies часто кращий вибір, ніж проста асерція

Швидкий приклад

typescript
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 підтримує два способи записати асерцію:

typescript
// Синтаксис as - працює скрізь, рекомендований const input = document.getElementById('email') as HTMLInputElement; // Синтаксис з кутовими дужками - не працює в JSX const input2 = <HTMLInputElement>document.getElementById('email');

У React TSX-файлах завжди використовуй as. Кутові дужки парсер сприймає як JSX-тег і видає помилку.

Головна різниця від приведення типів

Асерції існують лише на етапі компіляції. На відміну від Java або C#, де приведення реально конвертує значення, TypeScript просто прибирає асерцію при компіляції:

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
// TypeScript бачить: HTMLElement | null const element = document.getElementById('root'); // Non-null асерція - ти впевнений, що елемент є element!.innerHTML = 'Hello';

Це найчастіше джерело runtime-крашів у TypeScript-проектах. Використовуй тільки коли дійсно впевнений у наявності значення, і надавай перевагу if-перевірці, коли є сумніви.

as const

as const відрізняється від звичайної асерції. Він фіксує значення в літеральному типі і робить все readonly:

typescript
// Без 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 перевіряє, що значення відповідає типу, але зберігає найвужчий виведений тип кожної властивості. Звичайна анотація або асерція розширює все:

typescript
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:

typescript
const value: string = "hello"; // Помилка: типи string і number не перетинаються // const num = value as number; // Подвійна асерція - компілюється, але майже завжди неправильно const num = value as unknown as number;

Подвійна асерція - сигнал, що структура типів вимагає перегляду, а не черговий інструмент в арсеналі.

Типові помилки

Асерція без перевірки на null:

typescript
// Ти припускаєш, що елемент є - але він може бути відсутній 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:

tsx
// Помилка парсера - React сприймає це як JSX-елемент const el = <HTMLInputElement>document.getElementById('foo'); // Правильно const el = document.getElementById('foo') as HTMLInputElement;

Асерція до структурно неповного типу:

typescript
interface User { name: string; age: number; } const obj = { name: 'Alice' } as User; console.log(obj.age); // undefined - TypeScript це пропустив

Структурна типізація дозволяє відсутні поля при асерції. Перевіряй структуру перед тим, як стверджувати тип.

Використання as any для приховування реальних помилок:

typescript
// Погано - всі перевірки вимкнено 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 } після того, як middleware express.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-краш, що чекає свого часу: схема може не відповідати реальним даним.

Приклади

Базовий: виведення довжини рядка

typescript
const data: any = "TypeScript"; const strLength = (data as string).length; console.log(strLength); // 10

Ти кажеш компілятору, що data це рядок, тому .length доступний. Під час виконання data і справді рядок - крашу немає. Якби data було 42, отримав би undefined, бо у чисел немає .length.

Середній: DOM-input в обробнику форми

typescript
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 асерція для конфіг-об'єктів

typescript
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 дає одночасно і перевірку типу, і точне виведення.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?