Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Відмінності між any та unknown у TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`any` та `unknown`** обидва приймають будь-яке значення у TypeScript, але `unknown` є типобезпечним. - `any` повністю вимикає перевірку типів - `unknown` вимагає звуження типу (через `typeof`, `instanceof` або type guard) перед використанням ```typescript const x: unknown = getData(); if (typeof x === 'string') x.toUpperCase(); // OK після звуження ``` **Головне:** `unknown` за замовчуванням для зовнішніх даних; `any` тільки як свідомий escape hatch.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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`: підключення нетипізованих бібліотек, тимчасово під час міграції (документуй це) - Жоден: якщо можна описати конкретний тип - завжди описуй ### Таблиця порівняння | Аспект | `any` | `unknown` | |---|---|---| | Перевірка типів | Вимкнена | Увімкнена | | Виклик методів / читання властивостей | Так, без помилок | Ні, до звуження типу | | Присвоєння типізованим змінним | Так, вільно | Ні, потрібен 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` особливо небезпечний у бібліотечному або спільному коді.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.