Skip to main content

Відмінності між any та unknown у TypeScript

any та unknown - обидва типи приймають будь-яке значення у TypeScript, але тільки unknown змушує перевірити тип перед використанням.

Теорія

TL;DR

  • any - це чистий чек: викликай будь-який метод, читай будь-яку властивість, без питань
  • unknown - це запломбована коробка: потрібно довести що всередині, перш ніж TypeScript дозволить щось робити
  • any вимикає перевірку типів; unknown її зберігає і вимагає type guard
  • Не знаєш тип - бери unknown. any тільки коли свідомо обходиш TypeScript

Швидкий приклад

typescript
function process(value: any) { value.toUpperCase(); // Помилки немає - але впаде якщо value число } function processSafe(value: unknown) { value.toUpperCase(); // Помилка: Object is of type 'unknown' if (typeof value === 'string') { value.toUpperCase(); // OK - тип звужено до string } }

processSafe змушує спочатку перевірити. process довіряє сліпо, і ця довіра падає в рантаймі.

Головна різниця

any каже TypeScript: "припини перевіряти це значення взагалі." Можна викликати будь-який метод, присвоїти будь-якій змінній, передати куди завгодно. unknown каже: "я не знаю що це." Зберігати і передавати можна, але викликати методи чи читати властивості - тільки після того як доведеш тип через type guard. Один тип вимикає систему, інший з нею співпрацює.

Коли використовувати

  • unknown: відповіді API, результат JSON.parse(), об'єкт помилки в catch, параметри із зовнішнього введення
  • any: підключення нетипізованих бібліотек, тимчасово під час міграції (документуй це)
  • Жоден: якщо можна описати конкретний тип - завжди описуй

Таблиця порівняння

Аспектanyunknown
Перевірка типівВимкненаУвімкнена
Виклик методів / читання властивостейТак, без помилокНі, до звуження типу
Присвоєння типізованим зміннимТак, вільноНі, потрібен type guard
Ловить помилки під час компіляціїНіТак
Вимагає type guardНіТак
Коли використовуватиСвідомий escape hatchЗа замовчуванням для невідомих типів

Як це обробляє компілятор

TypeScript розглядає any як маркер "вимкнути перевірку": де зустрічає any, пропускає будь-яку валідацію для цього значення в обидва боки. unknown - навпаки: приймає будь-яке значення, але блокує доступ до нього поки тип не звужено. В рантаймі обидва типи стираються до звичайного JavaScript, тому різниці в продуктивності немає.

Типові помилки

Помилка 1: any там де маєш на увазі "не знаю тип"

typescript
// Неправильно function handleData(data: any) { return data.value * 2; // Впаде якщо data рядок } // Правильно function handleData(data: unknown) { if (typeof data === 'object' && data !== null && 'value' in data) { const val = data.value; if (typeof val === 'number') return val * 2; } throw new Error('Невалідна структура даних'); }

Помилка 2: any розповзається по сигнатурах функцій

typescript
// Неправильно - any на вході, any на виході, перевірок немає function process(input: any): any { return input.transform(); } // Правильно - звужуй на межі, повертай конкретний тип function process(input: unknown): string { if (typeof input === 'string') return input.toUpperCase(); throw new Error('Очікується рядок'); }

Помилка 3: забувають що помилки в catch мають тип unknown з TypeScript 4.0+

typescript
// Неправильно try { doSomething(); } catch (e) { console.log(e.message); // Помилка: e має тип unknown } // Правильно try { doSomething(); } catch (e) { if (e instanceof Error) { console.log(e.message); // Безпечно } }

Помилка 4: any у властивостях об'єкта як ярлик

typescript
// Неправильно - весь об'єкт стає нетипізованим interface Config { settings: any; } // Краще - ключі рядки, значення звужуються за потреби interface Config { settings: Record<string, unknown>; }

Коли я замінив весь any на unknown в одному проекті, TypeScript за першу годину виявив три баги, які тихо жили в продакшені місяцями.

Де зустрічається в реальному коді

  • Express: тіло запиту unknown до валідації в middleware
  • JSON.parse(): стандартна бібліотека повертає any, але обгортати в unknown - безпечніша практика
  • Redux: payload в actions часто unknown, звужується через discriminated union у reducer
  • React: обробники подій - event.target потребує звуження перед зверненням до властивостей
  • Zod / io-ts: обидві бібліотеки приймають unknown на вхід і повертають типізований результат після валідації

Питання на співбесіді

Q: Чи можна присвоїти unknown в any і навпаки?


A: Так, в обидва боки. any приймає все, unknown теж приймає будь-яке значення при присвоєнні. Але присвоїти unknown конкретному типу на зразок string без type guard не можна.

Q: Чим unknown відрізняється від узагальненого параметра типу <T>?


A: unknown - "якийсь тип, доведи який." <T> - "конкретний тип, який визначить той хто викликає функцію." Generics зберігають інформацію про тип через весь виклик; unknown відкидає її поки не звужено.

Q: Що змінилося в TypeScript 4.0 щодо цих типів?


A: Змінна в catch отримала тип unknown за замовчуванням через useUnknownInCatchVariables, яка автоматично вмикається в strict mode з версії 4.4. До цього перехоплена помилка мала тип any, тому старий код без перевірок у catch компілювався без зауважень.

Q: Коли використовувати unknown у generic-обмеженні замість параметра типу?


A: Коли хочеш змусити того хто викликає функцію надати type guard. function validate<T>(value: unknown, guard: (v: unknown) => v is T): T безпечніше ніж function validate<T>(value: T): T, бо явно обробляє невідомий вхід і вимагає guard для отримання типізованого результату.

Приклади

Обробка відповіді API з type guard

typescript
// З any - без безпеки async function fetchUser(id: string): Promise<any> { const response = await fetch(`/api/users/${id}`); return response.json(); } const user = await fetchUser('123'); console.log(user.email); // Помилки немає, але може бути undefined в рантаймі // З unknown - безпечно async function fetchUserSafe(id: string): Promise<unknown> { const response = await fetch(`/api/users/${id}`); return response.json(); } function isUser(val: unknown): val is { id: string; email: string } { return ( typeof val === 'object' && val !== null && 'id' in val && 'email' in val && typeof (val as Record<string, unknown>).id === 'string' && typeof (val as Record<string, unknown>).email === 'string' ); } const data = await fetchUserSafe('123'); if (isUser(data)) { console.log(data.email); // Повністю типобезпечно }

Type guard пишеться один раз і дає типізоване значення скрізь де потрібно. Це краще ніж пропускати перевірку взагалі.

any обходить generic-обмеження, unknown - ні

typescript
function processArray<T extends string>(arr: T[]): void { arr.forEach(item => console.log(item.toUpperCase())); } const anyValue: any = [1, 2, 3]; processArray(anyValue); // TypeScript не проти - падає в рантаймі const unknownValue: unknown = [1, 2, 3]; processArray(unknownValue); // TypeScript помилка - виявлено до рантайму

any непомітно проходить крізь generic-обмеження. unknown - ні. Саме через це any особливо небезпечний у бібліотечному або спільному коді.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?