Відмінності між any та unknown у TypeScript
any та unknown - обидва типи приймають будь-яке значення у TypeScript, але тільки unknown змушує перевірити тип перед використанням.
Теорія
TL;DR
any- це чистий чек: викликай будь-який метод, читай будь-яку властивість, без питаньunknown- це запломбована коробка: потрібно довести що всередині, перш ніж TypeScript дозволить щось робитиanyвимикає перевірку типів;unknownїї зберігає і вимагає type guard- Не знаєш тип - бери
unknown.anyтільки коли свідомо обходиш 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: підключення нетипізованих бібліотек, тимчасово під час міграції (документуй це)- Жоден: якщо можна описати конкретний тип - завжди описуй
Таблиця порівняння
| Аспект | any | unknown |
|---|---|---|
| Перевірка типів | Вимкнена | Увімкнена |
| Виклик методів / читання властивостей | Так, без помилок | Ні, до звуження типу |
| Присвоєння типізованим змінним | Так, вільно | Ні, потрібен type guard |
| Ловить помилки під час компіляції | Ні | Так |
| Вимагає type guard | Ні | Так |
| Коли використовувати | Свідомий escape hatch | За замовчуванням для невідомих типів |
Як це обробляє компілятор
TypeScript розглядає any як маркер "вимкнути перевірку": де зустрічає any, пропускає будь-яку валідацію для цього значення в обидва боки. unknown - навпаки: приймає будь-яке значення, але блокує доступ до нього поки тип не звужено. В рантаймі обидва типи стираються до звичайного JavaScript, тому різниці в продуктивності немає.
Типові помилки
Помилка 1: any там де маєш на увазі "не знаю тип"
// Неправильно
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 розповзається по сигнатурах функцій
// Неправильно - 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+
// Неправильно
try {
doSomething();
} catch (e) {
console.log(e.message); // Помилка: e має тип unknown
}
// Правильно
try {
doSomething();
} catch (e) {
if (e instanceof Error) {
console.log(e.message); // Безпечно
}
}Помилка 4: any у властивостях об'єкта як ярлик
// Неправильно - весь об'єкт стає нетипізованим
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
// З 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 - ні
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 особливо небезпечний у бібліотечному або спільному коді.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.