Как проверить тип класса в TypeScript: instanceof, type guards и защита типов
Основная проблема: динамическая проверка типов в статически типизированном языке
TypeScript — это статическая типизация, которая работает во время компиляции. Но во время выполнения JavaScript не знает о типах TypeScript. Поэтому для проверки типа класса в рантайме мы используем стандартные механизмы JavaScript, но оборачиваем их в TypeScript-конструкции, чтобы компилятор понимал, что происходит.
Решение 1: Оператор instanceof (самый простой и правильный)
Оператор instanceof проверяет, присутствует ли прототип класса в цепочке прототипов объекта. В TypeScript он работает как type guard — после проверки TypeScript автоматически сужает тип:
animals.forEach(animal => {
if (animal instanceof Dog) {
// TypeScript знает, что здесь animal — это Dog
animal.bark(); // ✅ OK
} else if (animal instanceof Cat) {
animal.meow(); // ✅ OK
}
});
Важно: instanceof работает только с классами (и конструкторами функций). Он не работает с интерфейсами или типами — они стираются во время компиляции.
Решение 2: User-defined type guards (пользовательские защитники типов)
Если логика проверки сложнее или вы хотите её переиспользовать, создайте функцию-предикат с возвращаемым типом animal is Dog:
function isDog(animal: Animal): animal is Dog {
return animal instanceof Dog;
}
function isCat(animal: Animal): animal is Cat {
return animal instanceof Cat;
}
animals.forEach(animal => {
if (isDog(animal)) {
animal.bark();
} else if (isCat(animal)) {
animal.meow();
}
});
Такая запись явно говорит TypeScript: "Если функция возвращает true, то переданный объект гарантированно имеет тип Dog". Это улучшает читаемость и позволяет использовать проверку в разных местах.
Решение 3: Дискриминирующие объединения (Discriminated Unions)
Если вы контролируете структуру классов, добавьте поле-дискриминатор (например, kind или type). Это самый надёжный способ, так как он не зависит от цепочки прототипов:
class Animal {
readonly kind: string;
name: string;
constructor(kind: string, name: string) {
this.kind = kind;
this.name = name;
}
}
class Dog extends Animal {
readonly kind = 'dog';
constructor(name: string) {
super('dog', name);
}
bark() { ... }
}
class Cat extends Animal {
readonly kind = 'cat';
constructor(name: string) {
super('cat', name);
}
meow() { ... }
}
// Type Guard с дискриминатором
function isDog(animal: Animal): animal is Dog {
return animal.kind === 'dog';
}
Чего делать НЕ стоит
- Не используйте
typeof— для экземпляров классов он всегда возвращает"object". - Не проверяйте по названию конструктора (
animal.constructor.name === 'Dog') — это ненадёжно при минификации кода. - Не используйте
as(type assertion) без проверки — это подавит ошибки компиляции, но не защитит от ошибок в рантайме.
Итог: когда что использовать?
- Если у вас простые классы и вы уверены в цепочке прототипов — используйте
instanceof. - Если логика проверки сложная или переиспользуется — оберните в user-defined type guard.
- Если вы проектируете систему с нуля — используйте дискриминирующие объединения (поле
kind). Это самый типобезопасный и предсказуемый подход.
Альтернативный подход: проверка по методам с помощью in оператора
Иногда instanceof не подходит — например, если объекты приходят из JSON (сериализованные) и не имеют прототипной связи с классами. В таких случаях можно проверить наличие специфических методов с помощью оператора in:
function isDog(animal: Animal): animal is Dog {
return 'bark' in animal;
}
function isCat(animal: Animal): animal is Cat {
return 'meow' in animal;
}
Плюсы:
- Работает даже с простыми объектами (plain objects), если они имеют нужные методы.
- Не требует наследования — подходит для duck typing.
Минусы:
- Ненадёжно, если метод может быть унаследован от другого класса или добавлен через прототип.
- Не защищает от коллизий имён (например, если у Cat тоже есть метод
bark).
Когда использовать?
Этот подход хорош для работы с данными из внешних источников (API, localStorage), где объекты не являются экземплярами ваших классов, но имеют схожую структуру. В таком случае лучше комбинировать проверку in с проверкой типов полей.
interface DogLike {
bark(): void;
name: string;
}
function isDogLike(obj: any): obj is DogLike {
return typeof obj.name === 'string' && typeof obj.bark === 'function';
}
Этот метод ближе к функциональному стилю и часто используется в связке с интерфейсами вместо классов.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги