Avatar ·

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

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

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

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

Как работает any

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

let value: any = "Hello";
console.log(value.toUpperCase()); // "HELLO" - работает
value = 42;
console.log(value.toUpperCase()); // Ошибка в рантайме! value.toUpperCase is not a function
value.someMethod(); // Тоже ошибка в рантайме, но компилятор молчит

Как работает unknown

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

let value: unknown = "Hello";
// value.toUpperCase(); // Ошибка компиляции: Object is of type 'unknown'
if (typeof value === "string") {
    console.log(value.toUpperCase()); // "HELLO" - безопасно, внутри проверки
}
// Или с приведением:
console.log((value as string).toUpperCase()); // "HELLO" - но осторожно, если приведение неверное

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

  • Миграция с JavaScript: когда постепенно добавляете типы в старый проект, any может быть временным решением.
  • Прототипирование: для быстрых экспериментов, когда типы только мешают.
  • Специфические случаи: например, при работе с eval() или динамическими импортами, где тип действительно неизвестен и неважен.

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

  • Данные из внешних источников: API, JSON-парсинг, ввод пользователя — всегда используйте unknown.
  • Библиотеки без типов: когда подключаете JS-библиотеку без определений типов.
  • Обобщенные функции: если функция принимает данные любого типа, но должна их обрабатывать безопасно.

Правильная обработка unknown

Вместо "кучи проверок" используйте type guards (защитники типа) — функции, которые проверяют и сужают тип:

function isString(value: unknown): value is string {
    return typeof value === "string";
}

function processUserData(data: unknown) {
    if (isString(data)) {
        // Здесь data — точно string
        console.log(data.toUpperCase());
    } else if (Array.isArray(data)) {
        // Здесь data — точно массив
        data.forEach(item => console.log(item));
    } else {
        console.log("Неизвестный тип данных");
    }
}

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

async function fetchUserData(id: number): Promise<unknown> {
    const response = await fetch(`/api/users/${id}`);
    return response.json(); // Мы не знаем, что придет
}

async function displayUserName(id: number) {
    const data = await fetchUserData(id);
    
    // Безопасная обработка
    if (typeof data === "object" && data !== null && "name" in data) {
        const userData = data as { name: string };
        console.log(`User name: ${userData.name}`);
    } else {
        console.error("Некорректный формат данных");
    }
}

Итог

  • any — для быстрого кода, когда типы не важны (но это рискованно).
  • unknown — для безопасного кода, когда тип неизвестен, но его нужно проверить.
  • Всегда предпочитайте unknown вместо any для внешних данных.
  • Используйте type guards и утверждения типов (as) для работы с unknown.

Помните: TypeScript создан, чтобы ловить ошибки на этапе компиляции. unknown помогает это делать, а any — прячет проблемы до рантайма.

<<>>

Альтернативный подход: утилитарные функции для работы с unknown

Вместо того чтобы писать проверки в каждом месте, можно создать набор вспомогательных функций, которые упростят работу с unknown. Это сделает код чище и переиспользуемее.

Утилиты для валидации

// Функция-валидатор с возвратом результата
function safeString(value: unknown): string | null {
    return typeof value === "string" ? value : null;
}

function safeNumber(value: unknown): number | null {
    return typeof value === "number" && !isNaN(value) ? value : null;
}

// Использование
function processData(data: unknown) {
    const name = safeString(data);
    if (name) {
        console.log(`Привет, ${name}!`);
    }
    
    const age = safeNumber(data);
    if (age !== null) {
        console.log(`Возраст: ${age}`);
    }
}

Деструктуризация с проверкой

Для объектов из API удобно использовать деструктуризацию с проверкой:

function extractUserInfo(data: unknown): { name?: string; email?: string } {
    if (typeof data !== "object" || data === null) return {};
    
    const obj = data as Record<string, unknown>;
    return {
        name: safeString(obj.name),
        email: safeString(obj.email)
    };
}

// Теперь обработка проста:
const user = extractUserInfo(apiResponse);
if (user.name) {
    console.log(user.name.toUpperCase());
}

Преимущества подхода

  • Переиспользование: функции-валидаторы можно использовать в разных частях проекта.
  • Читаемость: код становится понятнее, чем куча вложенных if.
  • Тестируемость: логику валидации легко тестировать отдельно.
  • Безопасность: вы контролируете, какие типы допускаете, и избегаете неожиданных ошибок.

Этот подход особенно полезен в больших проектах, где данные из API проходят через множество функций. Вместо того чтобы везде писать typeof проверки, вы создаете слой валидации, который делает код надежным и поддерживаемым.