← Назад к вопросам

Что такое Type Guard?

2.0 Middle🔥 111 комментариев
#TypeScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Type Guard в TypeScript: что это, как использовать, примеры

Что такое Type Guard

Type Guard — это функция или выражение в TypeScript, которое гарантирует для компилятора, что переменная имеет определённый тип в определённом месте кода.

Зачем это нужно:

  • TypeScript не может автоматически определить тип переменной в runtime
  • Type Guard помогает компилятору понять, какой тип у переменной
  • Это даёт нам type safety и автодополнение в IDE

Простейший Type Guard: typeof

// Функция, которая может принять число или строку
function processValue(value: number | string) {
  // Без type guard - TypeScript не знает, какой метод доступен
  // value.toUpperCase(); // Ошибка: число не имеет toUpperCase()

  // С type guard
  if (typeof value === 'string') {
    console.log(value.toUpperCase()); // ✅ TypeScript знает, что это string
  } else {
    console.log(value.toFixed(2)); // ✅ TypeScript знает, что это number
  }
}

Type Guard с instanceof

Для проверки типа объектов/классов:

// Определяем классы
class User {
  constructor(public name: string) {}
}

class Admin extends User {
  constructor(name: string, public permissions: string[]) {
    super(name);
  }
}

// Type Guard используя instanceof
function processUser(user: User | Admin) {
  if (user instanceof Admin) {
    // Здесь TypeScript знает, что это Admin
    console.log(`Admin ${user.name} has permissions:`, user.permissions);
  } else {
    // Здесь TypeScript знает, что это User
    console.log(`User ${user.name}`);
  }
}

Custom Type Guard: функция с type predicate

Это самый мощный способ создавать Type Guard'ы:

// Интерфейсы
interface Dog {
  type: 'dog';
  bark(): void;
}

interface Cat {
  type: 'cat';
  meow(): void;
}

// Type Guard функция
function isDog(animal: Dog | Cat): animal is Dog {
  return animal.type === 'dog';
}

function isCat(animal: Dog | Cat): animal is Cat {
  return animal.type === 'cat';
}

// Использование
const pet: Dog | Cat = { type: 'dog', bark: () => console.log('Woof!') };

if (isDog(pet)) {
  pet.bark(); // ✅ TypeScript знает, что это Dog
} else if (isCat(pet)) {
  pet.meow(); // ✅ TypeScript знает, что это Cat
}

Type Guard в реальных сценариях

Сценарий 1: Обработка API response'ов

// Ответы от API могут быть разных типов
interface SuccessResponse {
  status: 'success';
  data: unknown;
}

interface ErrorResponse {
  status: 'error';
  error: string;
}

type APIResponse = SuccessResponse | ErrorResponse;

// Type Guard
function isSuccess(response: APIResponse): response is SuccessResponse {
  return response.status === 'success';
}

// Использование
async function fetchData(): Promise<APIResponse> {
  const response = await fetch('/api/data');
  return response.json();
}

async function handleData() {
  const response = await fetchData();

  if (isSuccess(response)) {
    console.log('Data:', response.data); // ✅ TypeScript знает структуру
  } else {
    console.error('Error:', response.error); // ✅ TypeScript знает структуру
  }
}

Сценарий 2: Обработка разных типов ошибок

class ValidationError extends Error {
  constructor(public field: string, public message: string) {
    super(`${field}: ${message}`);
  }
}

class DatabaseError extends Error {
  constructor(public code: string) {
    super(`Database error: ${code}`);
  }
}

// Type Guard
function isValidationError(error: Error): error is ValidationError {
  return error instanceof ValidationError;
}

function isDatabaseError(error: Error): error is DatabaseError {
  return error instanceof DatabaseError;
}

// Использование
try {
  // code
} catch (error) {
  if (error instanceof Error) {
    if (isValidationError(error)) {
      console.log(`Validation failed on field: ${error.field}`);
    } else if (isDatabaseError(error)) {
      console.log(`Database error: ${error.code}`);
    } else {
      console.log(`Unknown error: ${error.message}`);
    }
  }
}

Сценарий 3: Nullish coalescing (проверка на null/undefined)

// Функция может вернуть пользователя или null
function getUser(id: string): User | null {
  // ...
}

// Type Guard для проверки на null
function isUser(value: User | null): value is User {
  return value !== null && value !== undefined;
}

// Использование
const user = getUser('123');

if (isUser(user)) {
  console.log(user.name); // ✅ TypeScript знает, что это не null
} else {
  console.log('User not found');
}

// Или более современный способ
if (user) {
  console.log(user.name);
}

Type Guard для Discriminated Union

Это очень полезная техника:

// Discriminated Union - заказ может быть в разных статусах
type OrderState =
  | { status: 'pending'; estimatedTime: Date }
  | { status: 'shipped'; trackingNumber: string }
  | { status: 'delivered'; deliveryDate: Date }
  | { status: 'cancelled'; reason: string };

// Type Guard'ы
function isPending(order: OrderState): order is OrderState & { status: 'pending' } {
  return order.status === 'pending';
}

function isShipped(order: OrderState): order is OrderState & { status: 'shipped' } {
  return order.status === 'shipped';
}

function isDelivered(order: OrderState): order is OrderState & { status: 'delivered' } {
  return order.status === 'delivered';
}

function isCancelled(order: OrderState): order is OrderState & { status: 'cancelled' } {
  return order.status === 'cancelled';
}

// Использование
function handleOrder(order: OrderState) {
  if (isPending(order)) {
    console.log(`Order will be ready in ${order.estimatedTime}`);
  } else if (isShipped(order)) {
    console.log(`Track your package: ${order.trackingNumber}`);
  } else if (isDelivered(order)) {
    console.log(`Delivered on ${order.deliveryDate}`);
  } else if (isCancelled(order)) {
    console.log(`Order cancelled: ${order.reason}`);
  }
}

Type Guard vs Type Assertion

Важное различие:

// ❌ Type Assertion - просто говорим компилятору, верить нам
const value: any = 'hello';
const length = (value as string).length; // 5

// Но если value на самом деле число - ошибка в runtime!
const value2: any = 123;
const length2 = (value2 as string).length; // undefined

// ✅ Type Guard - проверяем тип в runtime
function getValue(value: any) {
  if (typeof value === 'string') {
    console.log(value.length); // 5
  } else if (typeof value === 'number') {
    console.log('Not a string'); // Not a string
  }
}

Type Guard в функциях фильтрации

// Массив с разными типами
const values: (string | number | null)[] = ['hello', 42, null, 'world', 123];

// Type Guard для фильтрации
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

// Использование с filter
const strings = values.filter(isString);
// Тип: string[]
// Значение: ['hello', 'world']

// Или в одну строку
const numbers = values.filter((v): v is number => typeof v === 'number');
// Тип: number[]
// Значение: [42, 123]

Практическое применение в сервисах

// services/PaymentService.ts
type PaymentMethod = 
  | { type: 'credit_card'; cardNumber: string; cvv: string }
  | { type: 'paypal'; email: string }
  | { type: 'crypto'; address: string };

// Type Guard'ы
function isCreditCard(payment: PaymentMethod): payment is PaymentMethod & { type: 'credit_card' } {
  return payment.type === 'credit_card';
}

function isPayPal(payment: PaymentMethod): payment is PaymentMethod & { type: 'paypal' } {
  return payment.type === 'paypal';
}

function isCrypto(payment: PaymentMethod): payment is PaymentMethod & { type: 'crypto' } {
  return payment.type === 'crypto';
}

// Сервис
class PaymentService {
  async processPayment(amount: number, method: PaymentMethod) {
    if (isCreditCard(method)) {
      return this.processCreditCard(amount, method.cardNumber, method.cvv);
    } else if (isPayPal(method)) {
      return this.processPayPal(amount, method.email);
    } else if (isCrypto(method)) {
      return this.processCrypto(amount, method.address);
    }
  }

  private processCreditCard(amount: number, cardNumber: string, cvv: string) {
    // Логика для кредитной карты
  }

  private processPayPal(amount: number, email: string) {
    // Логика для PayPal
  }

  private processCrypto(amount: number, address: string) {
    // Логика для крипто
  }
}

Встроенные Type Guard'ы TypeScript

// Проверка на массив
function isArray(value: unknown): value is unknown[] {
  return Array.isArray(value);
}

// Проверка на объект
function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

// Проверка на функцию
function isFunction(value: unknown): value is Function {
  return typeof value === 'function';
}

// Использование
const data = { name: 'John' };
if (isObject(data)) {
  console.log(data.name); // ✅ Доступно
}

Лучшие практики Type Guard'ов

  1. Всегда возвращай правильный тип в predicate

    // ✅ Хорошо
    function isUser(value: unknown): value is User {
      return typeof value === 'object' && value !== null && 'id' in value;
    }
    
  2. Используй для дискриминирующих объединений

    // ✅ Идеально для Discriminated Union
    type Result = { success: true; data: string } | { success: false; error: Error };
    
  3. Не используй Type Assertion вместо Type Guard

    // ❌ Опасно
    const value = getData() as User;
    
    // ✅ Безопасно
    if (isUser(value)) {
      // использовать value как User
    }
    
  4. Кэшируй результаты Type Guard'ов

    const isPayment = isValidPayment(data);
    if (isPayment) {
      processPayment(data); // data правильного типа
    }
    

Выводы

Type Guard — это:

  • Способ помочь TypeScript понять типы в runtime
  • Используется для проверки union types
  • Даёт type safety и лучше автодополнение
  • Может быть простым (typeof, instanceof) или сложным (custom функции)
  • Особенно полезен для Discriminated Union паттернов
  • Предпочтительнее Type Assertion'ов

Типичное использование:

type Data = A | B | C;

if (isA(data)) {
  // работаем с A
} else if (isB(data)) {
  // работаем с B
} else {
  // работаем с C
}
Что такое Type Guard? | PrepBro