Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке typeguard у TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Type guard** - це перевірка під час виконання, яка звужує union-тип до конкретного підтипу всередині блоку коду. ```typescript function isUser(obj: unknown): obj is { name: string } { return typeof obj === "object" && obj !== null && "name" in obj; } if (isUser(data)) { console.log(data.name); // TypeScript знає форму об'єкта тут } ``` **Ключове:** ключове слово `is` у типі повернення запускає звуження TypeScript. Звичайне `boolean` нічого не дає.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Type guard** (захист типу) - це перевірка під час виконання, яка повідомляє компілятору TypeScript точний тип значення всередині конкретного блоку коду, звужуючи [union-тип](/questions/union-types-typescript) до точнішого підтипу. ## Теорія ### TL;DR - Уяви type guard як контрольно-пропускний пункт: після перевірки TypeScript точно знає, що всередині значення - Без guard TypeScript бачить `string | number` як обидва варіанти одночасно; з guard кожна гілка отримує один точний тип - Вбудовані варіанти: `typeof` для примітивів, `instanceof` для екземплярів класів, `in` для перевірки наявності властивостей - Користувацький guard: функція, що повертає `param is SpecificType`, де ключове слово `is` обов'язкове - Правило вибору: маєш union-тип або `unknown`/`any`? Додай guard перед зверненням до специфічних для типу властивостей ### Швидкий приклад ```typescript function printLength(value: string | number) { if (typeof value === "string") { // TypeScript знає: тут value є string console.log(value.length); // працює } else { // TypeScript знає: тут value є number console.log(value.toFixed(2)); // працює } } printLength("hello"); // 5 printLength(3.14); // "3.14" ``` `typeof value === "string"` - це і є guard. TypeScript читає цю перевірку і оновлює знання про тип лише для цієї гілки. Жодного casting-у, жодних помилок. ### Ключова різниця Без guard TypeScript не дозволяє викликати `.length` на `string | number`, бо не може гарантувати тип під час виконання. Guard дає компілятору доказ: всередині цього блоку я перевірив. Звуження типу живе тільки в цій області, після виходу з гілки union-тип повертається. ### Коли використовувати - **Union-типи в параметрах** - додай `typeof` або `instanceof` перед викликом методів, специфічних для типу - **Відповіді API з типом `unknown`** - перевір перед зверненням до будь-якої властивості на кшталт `user.name` - **Поліморфні компоненти** - використовуй `"onClick" in props` щоб відрізнити `ButtonProps` від `LinkProps` - **Пропусти**, якщо працюєш з одним конкретним типом - TypeScript вже знає - **Краще ніж `as`** - casting пропускає перевірку; guard доводить тип під час виконання ### Як це обробляє компілятор TypeScript виконує аналіз потоку управління (control flow analysis) під час перевірки типів. Він відстежує можливі типи по всіх гілках коду, розпізнає вбудовані guard-и за їх патерном, і перетинає вихідний union-тип з підтипом всередині гілки. Користувацькі guard-и працюють завдяки типу повернення `is`: TypeScript читає `param is SpecificType` як контракт і застосовує звуження в кожному місці виклику. Жодних додаткових витрат під час виконання - V8 просто виконує звичайний JavaScript. ### Типові помилки **Помилка 1: використання `in`, але виклик неправильного методу** ```typescript interface Cat { meow(): void; } interface Dog { bark(): void; } function pet(animal: Cat | Dog) { if ("meow" in animal) { animal.bark(); // Помилка: у Cat немає bark } } ``` `in` звужує за наявністю властивості. Він підтверджує що `meow` є, а не що `bark` є. Викликай те, що перевірив, або використовуй [discriminated union](/questions/discriminated-union-typescript) з полем-літералом `type`. **Помилка 2: відсутність предикату типу `is`** Це найчастіша проблема на код-рев'ю. Функція виглядає правильно, але звуження тихо перестає працювати. ```typescript // Повертає boolean - звуження не відбувається function isString(val: any): boolean { return typeof val === "string"; } if (isString(x)) { x.length; // TypeScript все одно бачить any тут } ``` TypeScript ігнорує звичайні `boolean` для звуження типів. Виправлення - одне слово: ```typescript function isString(val: any): val is string { return typeof val === "string"; } ``` **Помилка 3: `instanceof` для рядкових примітивів** ```typescript if (x instanceof String) { // Перевіряє об'єкт-обгортку, не примітив x.toUpperCase(); } ``` Примітиви не є екземплярами нічого. Для рядків, чисел і булевих значень використовуй `typeof`. **Помилка 4: перезапис змінної всередині guard-блоку** ```typescript let data: string | number = "test"; if (typeof data === "string") { data = 42; // перезапис скидає звуження } data.toFixed(); // Помилка компілятора - data знову string | number ``` Перезапис збиває відстеження потоку управління. Оголоси локальний `const` всередині блоку, якщо треба трансформувати значення. ### Де зустрічається - **React** - Mantine UI використовує `"onClick" in props` щоб рендерити `<button>` або `<a>` з одного поліморфного компонента - **Express** - `typeof req.body === "object"` перед зверненням до властивостей body в middleware - **Zod** - `schema.safeParse(data).success` автоматично звужує результат до інферованого типу - **Next.js** - `req.method === "POST"` звужує union методів перед парсингом тіла запиту - **Guard vs `as`** - використовуй guard для даних з API або від користувача; `as` тільки для статичних форм де впевнений на 100% ### Питання на співбесіді **Q:** Що таке користувацький type guard? **A:** Функція, що повертає `param is SpecificType`. TypeScript використовує предикат `is` для звуження типу в кожному місці виклику. Приклад: `function isUser(obj: any): obj is User { return typeof obj.name === "string"; }`. **Q:** Яка різниця між `in`, `typeof` та `instanceof`? **A:** `typeof` перевіряє примітивні типи: string, number, boolean, function. `instanceof` перевіряє екземпляри класів. `in` перевіряє наявність властивості на об'єкті - корисно для розрізнення інтерфейсів без спільного базового класу. **Q:** Як TypeScript обробляє вкладені type guard-и? **A:** Аналіз потоку управління відстежує типи через вкладені `if`-блоки, прогресивно звужуючи тип. Що глибше вкладеність, то вужчий тип у кожній гілці. **Q:** Чому звуження іноді ламається всередині циклу? **A:** TypeScript скидає звуження на кожній ітерації, бо змінна може бути перезаписана між ітераціями. Зафіксуй звужене значення в локальному `const` всередині тіла циклу. **Q:** Що таке discriminated union і як він пов'язаний з type guard-ами? (рівень senior) **A:** Discriminated union використовує спільне поле-літерал на кшталт `type: "success" | "error"` як дискримінант. `switch` по цьому полю автоматично звужує всю форму об'єкта. Це надійніше за перевірки властивостей і саме такий патерн використовує Redux Toolkit для action types. ## Приклади ### typeof: звуження union-типу примітивів ```typescript type Shape = string | number; function doubleIt(shape: Shape) { if (typeof shape === "number") { return shape * 2; // number } return shape.toUpperCase(); // string } console.log(doubleIt(5)); // 10 console.log(doubleIt("hello")); // "HELLO" ``` `typeof` - найпростіший guard. Після раннього return у гілці `number` TypeScript розуміє, що весь код нижче може бути тільки `string`. ### in: поліморфний React-компонент ```typescript interface ButtonProps { label: string; onClick: () => void; } interface LinkProps { href: string; children: string; } function InteractiveElement(props: ButtonProps | LinkProps) { if ("onClick" in props) { // звужено до ButtonProps return <button onClick={props.onClick}>{props.label}</button>; } // звужено до LinkProps return <a href={props.href}>{props.children}</a>; } ``` Одна функція, дві форми, жодного casting-у. Цей патерн зустрічається в бібліотеках компонентів на кшталт Mantine. ### Користувацький guard: валідація відповіді API ```typescript function isUser(obj: unknown): obj is { name: string; id: number } { return ( typeof obj === "object" && obj !== null && typeof (obj as any).name === "string" && typeof (obj as any).id === "number" ); } function processItem(item: unknown) { if (isUser(item)) { console.log(item.name.toUpperCase()); // звужено } else { console.log("Not a valid user"); } } processItem({ name: "Alice", id: 1 }); // "ALICE" processItem({}); // "Not a valid user" ``` Цей патерн використовує Zod всередині. Guard повертає `false` для всього що не відповідає схемі, тому помилки завжди обробляються безпечно без try/catch.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.