Типизация классов в TypeScript: как проверить тип объекта

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как проверить тип класса в TypeScript: от новичка до профи

Вы когда-нибудь писали на TypeScript, получали объект неизвестного типа и думали: "А кто ты, собственно, такой?"? Этот вопрос мучает тысячи разработчиков — не зря тема class type check in TypeScript собрала сотни тысяч просмотров на Stack Overflow. И сегодня мы раз и навсегда разберемся, как правильно проверять типы классов в TypeScript.

В этой статье вы узнаете:

  • Почему typeof не работает для классов
  • Как использовать instanceof как швейцарский нож
  • Что такое type guards и как они спасают ваш код
  • Как написать свою проверку типа с помощью is
  • Реальные примеры из практики

Поехали!

Проблема: почему typeof здесь бессилен?

В JavaScript есть оператор typeof, который отлично работает с примитивами:

typeof "hello" // "string"
typeof 42      // "number"
typeof true    // "boolean"

Но когда дело доходит до классов, typeof пасует:

class Dog {}
class Cat {}

const pet = new Dog();

typeof pet; // "object" — бесполезно!
typeof Dog; // "function" — тоже не то

Почему? Потому что классы в JavaScript — это просто синтаксический сахар над функциями-конструкторами. typeof видит только "объект" или "функцию", но не различает, какой именно это класс.

Здесь на сцену выходит class type check in TypeScript — целый арсенал инструментов, который поможет вам точно определить, с экземпляром какого класса вы имеете дело.

Метод 1: instanceof — ваш главный помощник

Самый прямой и понятный способ проверить тип класса — оператор instanceof. Он проверяет, есть ли класс в цепочке прототипов объекта:

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof!");
  }
}

class Cat extends Animal {
  meow() {
    console.log("Meow!");
  }
}

function handleAnimal(animal: Animal) {
  if (animal instanceof Dog) {
    // TypeScript знает, что animal — это Dog
    animal.bark();
  } else if (animal instanceof Cat) {
    animal.meow();
  } else {
    console.log(`Unknown animal: ${animal.name}`);
  }
}

const rex = new Dog("Rex");
handleAnimal(rex); // "Woof!"

Обратите внимание: после проверки animal instanceof Dog TypeScript автоматически сужает тип (type narrowing) и позволяет вызывать методы Dog без ошибок. Это и есть магия type guards — встроенных защитников типов.

Когда instanceof не работает?

  • При работе с разными окнами/фреймами (разные глобальные объекты)
  • Если классы определены в разных модулях с разными версиями
  • При проверке интерфейсов (они не существуют в рантайме)

Метод 2: Собственные type guards с оператором is

Иногда instanceof недостаточно. Например, вы хотите проверить не только класс, но и дополнительные условия. Здесь на помощь приходят пользовательские type guards с ключевым словом is:

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

function isFish(pet: Bird | Fish): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Bird | Fish) {
  if (isFish(pet)) {
    // TypeScript знает: pet — это Fish
    pet.swim();
  } else {
    pet.fly();
  }
}

Обратите внимание на синтаксис: pet is Fish в возвращаемом типе. Это говорит TypeScript: "Если эта функция возвращает true, то параметр pet — это Fish". Теперь TypeScript будет сужать тип везде, где вы используете isFish().

Проверка типа класса с дополнительной логикой

class User {
  constructor(
    public id: number,
    public name: string,
    public role: string
  ) {}
}

class Admin extends User {
  constructor(id: number, name: string) {
    super(id, name, "admin");
  }

  deleteUser(userId: number) {
    console.log(`Deleting user ${userId}`);
  }
}

function isAdmin(user: User): user is Admin {
  return user instanceof Admin && user.role === "admin";
}

function processUser(user: User) {
  if (isAdmin(user)) {
    // Безопасно вызываем метод Admin
    user.deleteUser(42);
  } else {
    console.log(`User ${user.name} has role: ${user.role}`);
  }
}

Такой class type check in TypeScript дает вам полный контроль: вы проверяете не только наследование, но и бизнес-логику.

Метод 3: Дискриминирующие объединения (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":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return (shape.base * shape.height) / 2;
  }
}

TypeScript автоматически сужает тип в каждой ветке switch или if. Этот подход часто предпочтительнее instanceof, потому что:

  • Работает с интерфейсами (нет рантайм-классов)
  • Не зависит от прототипов
  • Легче рефакторится

Метод 4: Проверка с помощью Symbol.hasInstance

Малоизвестный, но мощный способ — переопределить статическое свойство Symbol.hasInstance в классе. Это меняет поведение instanceof для вашего класса:

class SpecialNumber {
  static [Symbol.hasInstance](value: unknown) {
    return typeof value === "number" && value > 100;
  }
}

console.log(50 instanceof SpecialNumber);  // false
console.log(200 instanceof SpecialNumber); // true
console.log("hello" instanceof SpecialNumber); // false

Это экзотика, но иногда очень полезно, когда стандартная проверка типа класса не подходит.

Реальный пример: Система обработки событий

Давайте соберем все вместе в реальном сценарии. Представьте, что вы пишете библиотеку для обработки событий:

abstract class Event {
  constructor(
    public readonly timestamp: number = Date.now()
  ) {}
}

class ClickEvent extends Event {
  constructor(
    public x: number,
    public y: number
  ) {
    super();
  }
}

class KeyPressEvent extends Event {
  constructor(
    public key: string,
    public ctrlKey: boolean                

Блоги

Рекомендаций по книгам