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

Зачем нужен Type Guard?

1.2 Junior🔥 161 комментариев
#TypeScript

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

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

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

Type Guard в TypeScript

Type Guard — это механизм TypeScript, позволяющий сузить (narrow) тип переменной внутри определённого блока кода. Без Type Guard компилятор не знает конкретного типа переменной в runtime, и мы не можем безопасно обращаться к специфичным для этого типа свойствам или методам.

Зачем нужен Type Guard?

TypeScript работает на уровне статической типизации, но в runtime типы стираются. Когда у переменной тип string | number или Animal | Car, нельзя просто так вызвать метод, который есть только у одного из типов — компилятор выдаст ошибку. Type Guard решает эту проблему, давая компилятору понять: «внутри этого if-блока тип точно такой-то».

Виды Type Guard

1. typeof

Используется для примитивных типов (string, number, boolean, symbol, bigint, undefined, function).

function printValue(val: string | number) {
  if (typeof val === "string") {
    console.log(val.toUpperCase()); // val: string
  } else {
    console.log(val.toFixed(2)); // val: number
  }
}

2. instanceof

Используется для классов и конструкторов.

class Dog { bark() { return "Woof!"; } }
class Cat { meow() { return "Meow!"; } }

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    console.log(animal.bark()); // animal: Dog
  } else {
    console.log(animal.meow()); // animal: Cat
  }
}

3. in оператор

Проверяет наличие свойства в объекте. Удобен для union-типов с объектами.

interface Bird { fly(): void; }
interface Fish { swim(): void; }

function move(pet: Bird | Fish) {
  if ("fly" in pet) {
    pet.fly(); // pet: Bird
  } else {
    pet.swim(); // pet: Fish
  }
}

4. Пользовательский (user-defined) Type Guard

Функция, возвращающая predicate value is Type. Самый гибкий вариант.

interface Car { make: string; speed: number; }
interface Boat { name: string; knots: number; }

function isCar(vehicle: Car | Boat): vehicle is Car {
  return (vehicle as Car).speed !== undefined;
}

function describe(vehicle: Car | Boat) {
  if (isCar(vehicle)) {
    console.log(`Car: ${vehicle.make}, speed: ${vehicle.speed}`);
  } else {
    console.log(`Boat: ${vehicle.name}, knots: ${vehicle.knots}`);
  }
}

5. Discriminated Union (теговые объединения)

Общий паттерн: добавить поле-дискриминатор (kind, type, tag) в каждый вариант union.

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rect":
      return shape.width * shape.height;
  }
}

Assertion functions

Ещё один вариант — функции-ассерции (TypeScript 3.7+), которые сужают тип через выброс исключения:

function assertIsString(val: unknown): asserts val is string {
  if (typeof val !== "string") {
    throw new Error(`Expected string, got ${typeof val}`);
  }
}

function process(val: unknown) {
  assertIsString(val);
  console.log(val.toUpperCase()); // val: string
}

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

  • API responses: данные из fetch приходят как unknown, нужно проверить структуру перед использованием.
  • Event handlers: event.target часто типизирован как EventTarget, нужно сузить до HTMLInputElement.
  • Обработка ошибок: catch (e) возвращает unknown (TS 4+), необходимо проверить, что это Error.
try {
  // ...
} catch (e) {
  if (e instanceof Error) {
    console.error(e.message);
  }
}

Итог

Type Guard обеспечивает безопасное сужение типов без as-кастов (которые отключают проверки). Это позволяет писать код, который одновременно корректен с точки зрения типов и безопасен в runtime. Без Type Guard пришлось бы либо использовать небезопасные касты as, либо отказываться от union-типов вовсе.

Зачем нужен Type Guard? | PrepBro