Runtime помилки – це кошмар кожного розробника. Вони з’являються у продакшені, псують досвід користувачів і змушують нас витрачати час на дебаггінг. TypeScript обіцяє статичну типізацію, яка має допомогти нам їх уникнути, і type guards – один з ключових інструментів у цьому арсеналі. У цій статті ми розберемо, що таке type guards, як їх використовувати та як вони допомагають писати більш надійний TypeScript код.
Контекст і чому це важливо
Уявіть собі ситуацію: у вас є функція, яка обробляє дані, отримані з API. Ці дані можуть бути різного типу, залежно від обставин. Якщо ви не перевірите тип даних перед їх обробкою, ви можете отримати runtime помилку, наприклад, спробу виклик методу, якого не існує. Наприклад, спроба доступу до властивості `name` об’єкта, який насправді є `null` або `undefined`, призведе до помилки “Cannot read property ‘name’ of null”. TypeScript може допомогти виявити такі проблеми на етапі розробки, але іноді, через складність логіки, статична типізація не може повністю охопити всі можливі сценарії. Саме тут на допомогу приходять type guards.
Практична реалізація
Type guards – це функції або оператори, які дозволяють TypeScript звузити тип змінної на основі перевірки її значення. Вони повертають `true` або `false`, вказуючи, чи відповідає змінна певному типу. TypeScript використовує ці результати для звуження типу змінної в блоці коду, де type guard повернув `true`. Це дозволяє нам безпечно працювати з даними, знаючи їхній тип.
interface Bird {
fly(): void;
layEggs(): void;
}
interface Dog {
bark(): void;
fetch(): void;
}
type Animal = Bird | Dog;
function isBird(animal: Animal): animal is Bird {
return (animal as Bird).fly !== undefined;
}
function makeSound(animal: Animal) {
if (isBird(animal)) {
animal.fly(); // TypeScript знає, що animal - Bird
} else {
animal.bark(); // TypeScript знає, що animal - Dog
}
}
const myBird: Animal = {
fly: () => console.log("Bird is flying"),
layEggs: () => console.log("Bird is laying eggs")
};
const myDog: Animal = {
bark: () => console.log("Dog is barking"),
fetch: () => console.log("Dog is fetching")
};
makeSound(myBird);
makeSound(myDog);
У цьому прикладі `isBird` – це type guard. Він перевіряє, чи має об’єкт властивість `fly`. Якщо так, TypeScript звужує тип `animal` до `Bird` всередині блоку `if`. Це дозволяє нам безпечно викликати метод `fly` без ризику помилки. Аналогічно, в блоці `else` TypeScript знає, що `animal` є `Dog` і дозволяє викликати `bark`. `animal is Bird` – це type predicate, який повідомляє TypeScript, що функція `isBird` повертає `true` тільки якщо `animal` є `Bird`.
Поширені помилки та підводні камені
- Неправильне використання `any` в type guard: Використання `any` в type guard робить його неефективним, оскільки TypeScript втрачає інформацію про тип. Це скасовує всі переваги статичної типізації. Замість `any` використовуйте більш конкретні типи.
- Неправильна логіка type guard: Якщо логіка type guard невірна, TypeScript може неправильно звузити тип, що призведе до помилок. Ретельно тестуйте свої type guards, щоб переконатися, що вони працюють правильно. Наприклад, якщо `isBird` поверне `true` для об’єкта, який насправді є `Dog`, ми отримаємо непередбачувану поведінку.
- Забування про обробку всіх можливих типів: Якщо у вас є union type з багатьма можливими типами, переконайтеся, що ви обробили всі з них у своїх type guards. Необроблені типи можуть призвести до runtime помилок.
Порівняння підходів
Раніше, для перевірки типів даних, часто використовували прості if-else конструкції з перевіркою на `null` або `undefined`. Наприклад, `if (animal !== null) { … }`. Такий підхід працює, але він не надає TypeScript достатньо інформації про тип даних, тому статична типізація не може допомогти настільки, як з type guards. Type guards дозволяють TypeScript знати тип даних в блоці коду, що дає змогу виявляти більше помилок на етапі розробки та покращує безпеку коду.
Висновки
Type guards – це потужний інструмент для написання безпечного та надійного TypeScript коду. Вони дозволяють нам звузити тип змінних на основі перевірки їхнього значення, що дає змогу виявляти помилки на етапі розробки та уникнути runtime помилок. Почніть використовувати type guards у своїх проєктах вже сьогодні, і ви побачите, як це покращить якість вашого коду та зменшить кількість проблем у продакшені.