Avatar ·

Как узнать тип переменной в TypeScript: typeof, instanceof и пользовательские проверки

Всем привет! Я только начал изучать TypeScript и столкнулся с проблемой: как во время выполнения программы узнать, какой тип имеет переменная? В обычном JavaScript я использовал `typeof`, но в TS это работает не всегда так, как я ожидаю. Вот пример кода, который меня смущает: ```typescript let value: string | number = "hello"; // Пытаюсь проверить тип if (typeof value === "string") { console.log(value.toUpperCase()); // Работает } value = 42; if (typeof value === "number") { console.log(value.toFixed(2)); // Работает } // А вот с объектами всё сложнее interface User { name: string; age: number; } interface Admin { name: string; role: string; } function processUser(user: User | Admin) { // Как здесь проверить, User это или Admin? if (typeof user === "object") { // Это не помогает // Как получить конкретный тип? } } ``` Я понимаю, что TypeScript — это надстройка над JS, и типы существуют только на этапе компиляции. Но как тогда правильно проверять типы в рантайме? Есть ли какие-то встроенные методы, кроме `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-файлы). Для внутренней логики приложения достаточно нативных методов, описанных в первом ответе.