Проверка типа класса в TypeScript: как определить, является ли объект экземпляром определённого класса?
Понимание проблемы: классы как значения и типы
Ошибка, которую вы получаете, возникает не из-за самого instanceof, а из-за того, как TypeScript обрабатывает импорты и объявления. Когда вы пишете if (animal instanceof Dog), TypeScript ожидает, что Dog — это значение (конструктор класса). Ошибка 'Dog' refers to a value, but is being used as a type here появляется, если Dog был импортирован или объявлен только как тип (например, import type { Dog } from './animals'), а не как класс. В вашем случае, если вы объявили классы в том же файле, instanceof должен работать. Давайте разберём правильный подход.
Решение: правильное использование instanceof
В TypeScript instanceof работает точно так же, как в JavaScript, при условии, что класс определён как значение (через class), а не как тип (type или interface). Вот исправленный код:
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 processAnimals(animals: Animal[]) {
for (const animal of animals) {
if (animal instanceof Dog) {
// TypeScript теперь знает, что animal — это Dog
animal.bark(); // ✅ OK
} else if (animal instanceof Cat) {
animal.meow(); // ✅ OK
} else {
console.log(`Just an animal: ${animal.name}`);
}
}
}
// Использование
const animals: Animal[] = [new Dog("Rex"), new Cat("Whiskers"), new Animal("Generic")];
processAnimals(animals);
// Вывод:
// Woof!
// Meow!
// Just an animal: Generic
Почему это работает?
instanceof является пользовательским защитником типа (type guard) в TypeScript. После проверки animal instanceof Dog TypeScript сужает тип animal с Animal до Dog внутри блока if. Это позволяет вызывать методы, специфичные для подкласса (bark(), meow()), без ошибок компиляции.
Если классы находятся в разных модулях
Если вы импортируете классы из другого файла, убедитесь, что используете обычный импорт, а не импорт типа:
// animals.ts
export class Dog extends Animal { /* ... */ }
// main.ts
import { Dog } from './animals'; // ✅ Правильно: импорт значения
// import type { Dog } from './animals'; // ❌ Ошибка: импорт только типа
if (animal instanceof Dog) { /* ... */ }
Дополнительные советы
- Порядок проверок важен: Если у вас есть подклассы, проверяйте сначала самые специфичные (например,
DogиCat), потом базовый класс (Animal). - Пользовательские type guards: Для более сложной логики можно создать свою функцию-защитник:
function isDog(animal: Animal): animal is Dog { return animal instanceof Dog; } if (isDog(animal)) { animal.bark(); } - Дискриминирующие объединения: Если у вас есть контроль над классами, можно добавить поле-дискриминатор:
class Dog extends Animal { readonly kind = 'dog' as const; bark() { /* ... */ } } class Cat extends Animal { readonly kind = 'cat' as const; meow() { /* ... */ } } if (animal.kind === 'dog') { animal.bark(); // TypeScript сужает тип }
Заключение
instanceof — это полностью рабочий и правильный способ проверки типа класса в TypeScript, если класс определён как значение. Убедитесь, что вы не используете import type для классов, и всё будет работать. TypeScript автоматически сужает тип после проверки, что даёт вам безопасный доступ к методам подкласса.
Альтернативный подход: проверка через конструктор
Иногда instanceof может не сработать, если объекты созданы в разных контекстах выполнения (например, в разных iframe или при использовании библиотек для сериализации). В таких случаях можно проверить конструктор объекта напрямую:
function processAnimals(animals: Animal[]) {
for (const animal of animals) {
if (animal.constructor === Dog) {
// Здесь TypeScript не сузит тип автоматически!
// Придётся использовать приведение:
(animal as Dog).bark();
} else if (animal.constructor === Cat) {
(animal as Cat).meow();
}
}
}
Недостатки этого подхода:
- TypeScript не может автоматически сузить тип после проверки
constructor, поэтому приходится использоватьas(приведение типа), что менее безопасно. - Не работает с наследованием: если
Dogнаследуется отAnimal,animal.constructor === Dogвернётtrueтолько для прямых экземпляровDog, а не для подклассовDog. - Может сломаться, если прототип класса был изменён.
Когда использовать: Только если instanceof недоступен из-за кросс-контекстных проблем. В обычной разработке на TypeScript всегда предпочитайте instanceof — он безопаснее, читаемее и даёт правильное сужение типов.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги