Как узнать тип переменной в TypeScript: typeof, instanceof и пользовательские проверки
Полный гайд по определению типов в TypeScript
Вы правильно заметили: TypeScript стирает типы во время компиляции, поэтому в рантайме доступны только нативные механизмы JavaScript. Однако есть несколько надёжных подходов для определения типов.
1. Оператор typeof — для примитивов
Работает для: string, number, boolean, symbol, undefined, bigint, function.
function process(value: string | number | boolean) {
if (typeof value === "string") {
// TypeScript сужает тип до string
console.log(value.toUpperCase());
} else if (typeof value === "number") {
console.log(value.toFixed(2));
} else {
// Остаётся только boolean
console.log(value ? "True" : "False");
}
}
2. Оператор instanceof — для классов
Проверяет, принадлежит ли объект определённому классу (работает через prototype chain).
class Car {
drive() { console.log("Vroom!"); }
}
class Bicycle {
ride() { console.log("Ring ring!"); }
}
function move(vehicle: Car | Bicycle) {
if (vehicle instanceof Car) {
vehicle.drive(); // TypeScript знает, что это Car
} else {
vehicle.ride(); // Остаётся Bicycle
}
}
3. Пользовательские защитники типа (Type Guards)
Самый гибкий способ — написать функцию-проверку с предикатом типа (value is Type).
interface User {
name: string;
age: number;
}
interface Admin {
name: string;
role: string;
}
// Пользовательский защитник типа
function isAdmin(user: User | Admin): user is Admin {
return (user as Admin).role !== undefined;
}
function processUser(user: User | Admin) {
if (isAdmin(user)) {
console.log(`Admin role: ${user.role}`); // user сужен до Admin
} else {
console.log(`User age: ${user.age}`); // user сужен до User
}
}
4. Дискриминирующие объединения (Discriminated Unions)
Лучший способ для сложных типов — добавить общее поле-дискриминатор.
// Добавляем поле type — "дискриминатор"
interface User {
type: "user";
name: string;
age: number;
}
interface Admin {
type: "admin";
name: string;
role: string;
}
type Person = User | Admin;
function processPerson(person: Person) {
switch (person.type) {
case "user":
console.log(`User, age: ${person.age}`);
break;
case "admin":
console.log(`Admin, role: ${person.role}`);
break;
}
}
5. Оператор in — проверка наличия свойства
if ("age" in user) {
// TypeScript понимает, что это User
console.log(user.age);
} else {
// Это Admin
console.log(user.role);
}
6. Фабрика защитников типа через typeof с объектами
function hasProperty<T extends object>(obj: T, prop: keyof any): prop is keyof T {
return prop in obj;
}
if (hasProperty(user, "age")) {
console.log(user.age);
}
Важно: Для интерфейсов и типов (не классов) НЕЛЬЗЯ использовать instanceof. Всегда используйте либо дискриминирующие объединения, либо пользовательские проверки с in или сравнением полей.
Рекомендация: Для новых проектов всегда используйте дискриминирующие объединения — это самый надёжный и производительный способ. Для легаси-кода подойдут пользовательские защитники.
<<Альтернативный подход: библиотеки для рантайм-валидации
Если вам нужно часто проверять сложные типы в рантайме, рассмотрите специализированные библиотеки. Они позволяют описывать схемы типов и проверять данные на соответствие.
1. Zod
import { z } from "zod";
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
const AdminSchema = z.object({
name: z.string(),
role: z.string(),
});
function process(data: unknown) {
if (UserSchema.safeParse(data).success) {
// data — это User
console.log(data.age);
} else if (AdminSchema.safeParse(data).success) {
console.log(data.role);
}
}
2. io-ts
import * as t from "io-ts";
const UserType = t.type({
name: t.string,
age: t.number,
});
const result = UserType.decode(someData);
if (result._tag === "Right") {
console.log(result.right.name);
}
Плюсы: автоматическая генерация TypeScript-типов из схемы, подробные сообщения об ошибках.
Минусы: дополнительная зависимость, увеличение размера бандла.
Используйте такие библиотеки, если работаете с данными из внешних источников (API, формы, JSON-файлы). Для внутренней логики приложения достаточно нативных методов, описанных в первом ответе.