Сегодня без долгих вступлений. Если вы пишете на TypeScript, для сохранения ментального здоровья вам просто необходимо уметь пользоваться тайп гардами — инструментом, который позволяет вам указать TypeScript, с каким именно типом он работает.
- Зачем нужны Type Guards?
- Базовые Type Guards
- 1. typeof — для примитивов
- 2. instanceof — для классов
- 3. in — для проверки свойств
- 4. Строгие проверки на null и undefined
- Пользовательские Type Guards
- Дополнительные техники
- 1. Дискриминируемые юнионы (Discriminated Unions)
- 2. Проверка массива
- 3. Проверка объединения с пользовательским предикатом
- Для чего вам могут пригодиться Type Guards?
- Итого
Зачем нужны Type Guards?
Без Type Guards TypeScript часто не может определить тип переменной в рантайме:
function processValue(value: string | number) {
// Ошибка! TS не знает, есть ли у value метод toUpperCase()
return value.toUpperCase(); // ❌ Property 'toUpperCase' does not exist on type 'string | number'
}
TypeScript буквально говорит: «Я не уверен, что это — строка или число. А что если число? У числа нет метода toUpperCase()».
Чтобы этот код сработал, мы должны как-то сузить тип и сказать TS: «Если это строка, то делай так. Если число — иначе»
Базовые Type Guards
1. typeof — для примитивов
typeof работает с примитивными типами: string, number, boolean, symbol, undefined, function, bigint.
function processValue(value: string | number) {
if (typeof value === 'string') {
// TS знает: здесь value — точно string
return value.toUpperCase();
} else {
// TS знает: здесь value — точно number
return value.toFixed(2);
}
}
processValue('hello'); // 'HELLO'
processValue(42); // '42.00'
TypeScript видит проверку typeof value === 'string' и внутри блока if сужает тип до string. В блоке else — до number.
2. instanceof — для классов
instanceof проверяет, является ли объект экземпляром класса.
class Dog {
bark() { console.log('Гав!'); }
}
class Cat {
meow() { console.log('Mяв!'); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
// TS знает: здесь animal — Dog
animal.bark();
} else {
// TS знает: здесь animal — Cat
animal.meow();
}
}
makeSound(new Dog()); // 'Гав!'
makeSound(new Cat()); // 'Мяв!'
3. in — для проверки свойств
in проверяет, есть ли у объекта определенное свойство.
interface Car {
drive(): void;
}
interface Boat {
sail(): void;
}
function move(vehicle: Car | Boat) {
if ('drive' in vehicle) {
// TS знает: здесь vehicle — Car (у него есть drive)
vehicle.drive();
} else {
// TS знает: здесь vehicle — Boat (у него нет drive, значит есть sail)
vehicle.sail();
}
}
4. Строгие проверки на null и undefined
function processName(name: string | null) {
if (name === null) {
return 'Имя не указано';
}
// TS знает: здесь name — точно string
return name.toUpperCase();
}
Пользовательские Type Guards
Иногда (и даже чаще всего) встроенных проверок недостаточно. Например, нужно проверить, что объект соответствует сложному интерфейсу.
interface User {
name: string;
age: number;
}
// Мы получили данные из API - это any или unknown
const data: unknown = JSON.parse('{"name":"Vasya","age":30}');
// Проверяем, что data - это User
function isUser(obj: unknown): obj is User {
// Проверяем, что obj - объект и у него есть поля name (string) и age (number)
return (
typeof obj === 'object' &&
obj !== null &&
'name' in obj &&
typeof obj.name === 'string' &&
'age' in obj &&
typeof obj.age === 'number'
);
}
if (isUser(data)) {
// TS знает: здесь data — User!
console.log(data.name.toUpperCase()); // 'VASYA'
}
Обратите внимание на тип возвращаемого нашим тайпгардом значения. obj is User — это предикат типа. Он говорит TypeScript: «Если эта функция возвращает true, то переменная obj имеет тип User«.
Дополнительные техники
1. Дискриминируемые юнионы (Discriminated Unions)
Про дискриминируемые юнионы мы уже говорили, но тут напомню вкратце. Это когда у всех типов в объединении есть общее свойство-дискриминант, по которому легко определить тип.
interface Circle {
kind: 'circle';
radius: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface Triangle {
kind: 'triangle';
base: number;
height: number;
}
type Shape = Circle | Rectangle | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
// TS знает: здесь shape — Circle
return Math.PI * shape.radius ** 2;
case 'rectangle':
// TS знает: здесь shape — Rectangle
return shape.width * shape.height;
case 'triangle':
// TS знает: здесь shape — Triangle
return (shape.base * shape.height) / 2;
}
}
TypeScript автоматически сужает тип на основе значения kind. И если вы добавите новый тип в Shape, но не добавите его в switch, TypeScript выплюнет ошибку (но это если включить exhaustive checking в tsconfig.json).
2. Проверка массива
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === 'string');
}
const data: unknown = ['a', 'b', 'c'];
if (isStringArray(data)) {
// TS знает: data — string[]
console.log(data.join(', ')); // 'a, b, c'
}
3. Проверка объединения с пользовательским предикатом
type ApiResponse =
| { status: 'success'; data: User }
| { status: 'error'; error: string };
function isSuccess(response: ApiResponse): response is { status: 'success'; data: User } {
return response.status === 'success';
}
function handleResponse(response: ApiResponse) {
if (isSuccess(response)) {
// TS знает: здесь response — success
console.log(response.data.name);
} else {
// TS знает: здесь response — error
console.log('Ошибка:', response.error);
}
}
Для чего вам могут пригодиться Type Guards?
- Валидация API-ответов — после
fetch()вы получаетеunknown. Проверяете через пользовательский Type Guard. - Работа с формами — проверка, что значение — строка, число, email и т.д.
- Redux Toolkit — в редьюсерах проверяете, какой экшен пришел.
- React-компоненты с пропсами разных типов — внутри компонента проверяете, что именно передано.
- Обработка ошибок — проверка, что ошибка — это экземпляр
Error, а не строка.
Итого
- Type Guard — способ сузить широкий тип (
string | number) до конкретного (string) - Встроенные:
typeof(примитивы),instanceof(классы),in(свойства). - Пользовательский Type Guard: функция, которая возвращает
value is Type. - Дискриминируемые юнионы — когда у всех типов есть общее поле, по которому можно определить тип.
- Используйте Type Guards для валидации API-ответов, проверки форм и обработки ошибок.
Используйте тайп гарды, чтобы избавиться от any и сделать ваш код типобезопасным. Желаю успехов!







