Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Типові асерції в TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Типові асерції (type assertions)** кажуть TypeScript вважати значення певним типом, не змінюючи його під час виконання. ```typescript const input = document.getElementById('email') as HTMLInputElement; input.value = 'test@example.com'; // Non-null асерція прибирає null | undefined const root = document.getElementById('root')!; ``` **Ключове:** асерції зникають при компіляції - жодних runtime-перевірок, жодної конвертації.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Типові асерції (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` дає одночасно і перевірку типу, і точне виведення.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.