Avatar ·

Типы `unknown` и `any` в TypeScript: в чем разница и когда что использовать?

Всем привет! Я недавно начал изучать TypeScript и столкнулся с двумя типами, которые на первый взгляд кажутся очень похожими: `any` и `unknown`. Оба позволяют хранить значения любого типа, но я чувствую, что между ними есть какая-то важная разница, которую я упускаю. Например, я пишу функцию, которая получает данные от API. Я не знаю заранее, что именно придет — может быть объект, строка или число. Вот как я это делаю сейчас: ```typescript function processData(data: any) { console.log(data.toUpperCase()); // Работает без ошибок, но в рантайме может упасть } ``` Но если я попробую использовать `unknown`, то получаю ошибку: ```typescript function processDataSafe(data: unknown) { console.log(data.toUpperCase()); // Ошибка: Object is of type 'unknown' } ``` Почему так? Вроде бы оба типа позволяют хранить что угодно, но `unknown` ведет себя как-то строже. Я читал, что `unknown` — это type-safe альтернатива `any`, но не до конца понимаю, что это значит на практике. Когда мне стоит использовать `any`, а когда `unknown`? И какие подводные камни есть у каждого из них? Буду благодарен за подробное объяснение с примерами! <<>>

Ключевое отличие: безопасность типов

Главное различие между any и unknown заключается в том, как TypeScript обрабатывает эти типы при выполнении операций. any полностью отключает проверку типов, в то время как unknown требует явной проверки перед использованием.

Тип any: "отключи TypeScript"

Когда вы используете any, вы говорите компилятору: "Не проверяй этот тип, я знаю, что делаю". Это означает, что вы можете вызывать любые методы и свойства на значении типа any без ошибок компиляции, даже если эти методы не существуют:

let value: any = "hello";
value.toUpperCase();       // ✅ OK (но в рантайме действительно существует)
value.nonExistentMethod(); // ✅ OK в TypeScript, но ❌ ошибка в рантайме
value.some.property.path;  // ✅ OK, но может упасть в рантайме

Проблема в том, что вы теряете все преимущества TypeScript — автодополнение, проверку типов, рефакторинг. Ошибки, которые могли быть найдены на этапе компиляции, переходят в рантайм.

Тип unknown: "безопасное хранилище"

unknown — это type-safe аналог any. Вы можете присвоить unknown что угодно, но не можете использовать значение без предварительной проверки типа:

let value: unknown = "hello";
value.toUpperCase(); // ❌ Ошибка: Object is of type 'unknown'

// Правильный подход — сузить тип через проверку
if (typeof value === "string") {
    value.toUpperCase(); // ✅ OK, теперь TypeScript знает, что это string
}

Когда что использовать?

  • Используйте unknown когда:
    • Получаете данные из внешних источников (API, пользовательский ввод)
    • Пишете функции, которые должны работать с разными типами, но требуют проверки
    • Хотите заставить других разработчиков (или себя) явно проверять типы
  • Используйте any только когда:
    • Мигрируете JavaScript-проект на TypeScript и временно не можете типизировать все
    • Работаете с очень динамичными структурами, где типизация невозможна
    • Нужно быстро прототипировать (но потом лучше заменить на unknown)

Практический пример: обработка API-ответа

// Плохо — с any
function processApiResponse(response: any) {
    // Никакой защиты, любая опечатка приведет к ошибке
    console.log(response.user.name.toUpperCase());
}

// Хорошо — с unknown
function processApiResponseSafe(response: unknown) {
    if (response && typeof response === "object" && "user" in response) {
        const user = (response as { user: { name: string } }).user;
        if (typeof user.name === "string") {
            console.log(user.name.toUpperCase());
        }
    }
}

Дополнительные возможности: type guards и утверждения

Вы можете создавать собственные type guards для работы с unknown:

function isUser(obj: unknown): obj is { name: string; age: number } {
    return (
        typeof obj === "object" &&
        obj !== null &&
        "name" in obj &&
        typeof (obj as any).name === "string" &&
        "age" in obj &&
        typeof (obj as any).age === "number"
    );
}

function processUser(data: unknown) {
    if (isUser(data)) {
        console.log(`User: ${data.name}, ${data.age}`); // ✅ Безопасно
    }
}

Вывод: Всегда отдавайте предпочтение unknown перед any. any — это "красная кнопка", которая отключает TypeScript. unknown — это безопасный контейнер, который заставляет вас явно проверять типы, делая код надежнее.

<<>>

Альтернативный взгляд: когда any все-таки оправдан

Хотя unknown почти всегда лучше, есть несколько сценариев, где any может быть осознанным выбором:

1. Сложные дженерики и декораторы

// Иногда нужно передать "что угодно" через цепочку функций
function pipe(...fns: Array<(arg: any) => any>): (arg: any) => any {
    return (arg: any) => fns.reduce((acc, fn) => fn(acc), arg);
}

Здесь any используется намеренно, потому что мы не можем предсказать все возможные комбинации типов.

2. Работа с Reflect API или Proxy

const handler: ProxyHandler<any> = {
    get(target: any, prop: string) {
        return prop in target ? target[prop] : undefined;
    }
};

3. Быстрая миграция с JavaScript

Если вы переносите большой JS-проект, можно временно использовать any для всего, что не удалось типизировать сразу. Но обязательно помечайте такие места комментарием // TODO: заменить на unknown.

Важное правило

Если вы используете any, спросите себя: "Могу ли я заменить его на unknown с type guard?" Если ответ "да" — сделайте это. Если "нет" — убедитесь, что у вас действительно веская причина использовать any, и задокументируйте её.