Пошук уроків, статей та іншого контенту
Зрозумійте ризики any, безпечну роботу з unknown і способи звуження невідомих значень перевірками.
any: вимкнення перевірки типівТип any означає: «це значення може мати будь-який тип». TypeScript майже не перевіряє операції з таким значенням.
let value: any = "TypeScript";
value.toUpperCase(); // коректно
value.toFixed(2); // помилка виникне лише під час виконання
value.notExisting(); // TypeScript також не повідомить про помилкуУ цьому прикладі TypeScript дозволяє викликати будь-які методи. Якщо під час виконання value залишається рядком, виклик toFixed завершиться помилкою:
TypeError: value.toFixed is not a functionany поширюється кодомЗначення типу any може передавати свою небезпечну поведінку іншим змінним:
const externalValue: any = "42";
const numberValue: number = externalValue;
numberValue.toFixed(2); // помилка під час виконанняTypeScript дозволив присвоєння, хоча фактичне значення є рядком, а не числом.
any також послаблює перевірку результатів функцій:
function getData(): any {
return {
name: "Ada"
};
}
const data = getData();
console.log(data.name.toUpperCase()); // працює
console.log(data.age.toFixed(0)); // помилка під час виконанняany особливо небезпечнийРизик зростає, коли any використовується для:
даних із мережі або зовнішнього API;
значень із JSON;
результатів бібліотеки, типи якої невідомі;
параметрів функцій;
об’єктів, які передаються між кількома модулями.
Іноді any потрібен для поступової міграції старого JavaScript-коду або роботи з бібліотекою без типів. Проте його варто обмежувати невеликою ділянкою коду, а не поширювати на всю програму.
unknown: безпечна альтернативаunknown, як і any, може містити значення будь-якого типу. Але TypeScript не дозволяє виконувати операції з unknown, доки тип значення не буде перевірено.
let value: unknown = "TypeScript";
// value.toUpperCase();
// Помилка: властивість недоступна для значення типу unknownСпочатку потрібно звузити тип:
if (typeof value === "string") {
console.log(value.toUpperCase());
}Така перевірка гарантує, що всередині блоку value має тип string.
unknown можна присвоїти лише безпечному типуЗначення будь-якого типу можна присвоїти змінній unknown:
const text: string = "hello";
const count: number = 10;
const first: unknown = text;
const second: unknown = count;Але зворотне присвоєння потребує перевірки:
const value: unknown = "hello";
// const text: string = value;
// Помилка: unknown не можна безпосередньо присвоїти string
if (typeof value === "string") {
const text: string = value;
console.log(text);
}Це змушує обробити невідоме значення до того, як програма почне з ним працювати.
typeofОператор typeof підходить для примітивних типів:
"string"
"number"
"boolean"
"bigint"
"symbol"
"undefined"
"function"
"object"
function printValue(value: unknown): void {
if (typeof value === "string") {
console.log(`Рядок: ${value.toUpperCase()}`);
return;
}
if (typeof value === "number") {
console.log(`Число: ${value.toFixed(2)}`);
return;
}
if (typeof value === "boolean") {
console.log(`Логічне значення: ${value ? "так" : "ні"}`);
return;
}
console.log("Невідомий тип значення");
}
printValue("typescript");
printValue(42);
printValue(true);
printValue({ name: "Ada" });Після кожної перевірки TypeScript звужує тип value лише в межах відповідної гілки.
typeof nullУ JavaScript результат typeof null — "object":
console.log(typeof null); // "object"Тому для роботи з об’єктами потрібно окремо перевіряти null:
function printObject(value: unknown): void {
if (typeof value === "object" && value !== null) {
console.log("Це об'єкт");
}
}Перевірка value !== null важлива, якщо код очікує саме об’єкт.
instanceofinstanceof перевіряє, чи створене значення певним класом або конструктором:
class UserError extends Error {
constructor(message: string) {
super(message);
this.name = "UserError";
}
}
function printError(value: unknown): void {
if (value instanceof UserError) {
console.log(`Помилка користувача: ${value.message}`);
return;
}
if (value instanceof Error) {
console.log(`Інша помилка: ${value.message}`);
return;
}
console.log("Невідоме значення");
}
printError(new UserError("Некоректний пароль"));
printError(new Error("Помилка сервера"));Після перевірки value instanceof Error TypeScript знає, що значення має властивості та методи Error.
inОператор in перевіряє наявність властивості в об’єкті. Перед перевіркою потрібно переконатися, що значення є об’єктом і не є null.
function printMessage(value: unknown): void {
if (
typeof value === "object" &&
value !== null &&
"message" in value
) {
console.log(value.message);
}
}
printMessage({ message: "Готово" });
printMessage({ status: 200 });Перевірка "message" in value дозволяє TypeScript безпечніше звернутися до властивості message.
Однак сама наявність властивості ще не гарантує її тип. Якщо потрібно викликати метод рядка, перевірте і тип властивості:
function printUppercaseMessage(value: unknown): void {
if (
typeof value === "object" &&
value !== null &&
"message" in value &&
typeof value.message === "string"
) {
console.log(value.message.toUpperCase());
}
}Якщо одна й та сама перевірка використовується кілька разів, її можна винести в type predicate — функцію, яка повідомляє TypeScript результат перевірки.
Синтаксис type predicate:
value is SomeTypeНаприклад:
type User = {
id: number;
name: string;
};
function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) {
return false;
}
if (!("id" in value) || !("name" in value)) {
return false;
}
return (
typeof value.id === "number" &&
typeof value.name === "string"
);
}
function greet(value: unknown): void {
if (isUser(value)) {
// Тут value має тип User
console.log(`Привіт, ${value.name}!`);
} else {
console.log("Отримано значення, яке не є User");
}
}
greet({ id: 1, name: "Олена" });
greet({ id: "1", name: "Олена" });
greet("guest");Функція isUser не лише повертає boolean. Її тип value is User пояснює TypeScript, що після результату true значення можна використовувати як User.
Небезпечно створювати type predicate, який перевіряє лише частину структури:
type Product = {
id: number;
title: string;
};
function isProduct(value: unknown): value is Product {
// Неповна перевірка: title може мати неправильний тип
return typeof value === "object" && value !== null && "id" in value;
}Після такої функції TypeScript повірить, що value.title — це рядок, хоча перевірка цього не підтверджує. Type predicate має відповідати реальним умовам, які він перевіряє.
unknownФункція, яка приймає unknown, має явно визначити, що робити з кожним підтримуваним типом:
function formatInput(value: unknown): string {
if (typeof value === "string") {
return value.trim();
}
if (typeof value === "number") {
return value.toString();
}
if (typeof value === "boolean") {
return value ? "true" : "false";
}
return "Непідтримуване значення";
}
console.log(formatInput(" hello ")); // hello
console.log(formatInput(123)); // 123
console.log(formatInput(false)); // false
console.log(formatInput(["a", "b"])); // Непідтримуване значенняТакий підхід робить обробку зовнішніх даних передбачуваною: кожне значення проходить перевірку до використання.
unknown у catchПомилка, яку перехоплює catch, може бути будь-яким значенням: не лише екземпляром Error. Тому безпечно розглядати її як unknown:
function getErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;
}
if (typeof error === "string") {
return error;
}
return "Невідома помилка";
}
try {
throw new Error("Не вдалося завантажити дані");
} catch (error: unknown) {
console.log(getErrorMessage(error));
}У TypeScript параметр catch можна явно позначити як unknown. Це підкреслює, що перед читанням error.message потрібно перевірити, чи є значення екземпляром Error.
Іноді розробник точно знає тип значення, але TypeScript не може це довести. Тоді використовують приведення типу:
const value: unknown = "TypeScript";
const text = value as string;
console.log(text.toUpperCase());Приведення as string не перевіряє значення під час виконання. Воно лише повідомляє компілятору, що розробник бере відповідальність за цей тип.
const value: unknown = 42;
const text = value as string;
// Помилка під час виконання:
// text.toUpperCase is not a function
console.log(text.toUpperCase());Тому перевірка безпечніша за сліпе приведення:
const value: unknown = 42;
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
console.log("Значення не є рядком");
}Приведення типу доречне, коли тип гарантований зовнішньою логікою, але його неможливо виразити стандартною перевіркою. Для неперевірених зовнішніх даних краще використовувати звуження або власний type guard.
any та unknown| Властивість | any | unknown | |---|---|---| | Може містити будь-яке значення | Так | Так | | Дозволяє викликати методи без перевірки | Так | Ні | | Можна без перевірки присвоїти string або number | Так | Ні | | Змушує звужувати тип | Ні | Так | | Ризик помилки під час виконання | Високий | Нижчий |
У новому коді, коли тип значення справді невідомий, зазвичай слід обирати unknown. any варто залишати для конкретних випадків, де обмеження TypeScript свідомо вимикаються.
any замість перевіркиfunction printName(value: any): void {
console.log(value.name);
}Функція може отримати число, null або рядок. Безпечніший варіант — unknown і перевірка:
function printName(value: unknown): void {
if (
typeof value === "object" &&
value !== null &&
"name" in value &&
typeof value.name === "string"
) {
console.log(value.name);
}
}unknown і objectТип object означає значення, яке не є примітивом, але не описує його структуру:
const value: object = { name: "Ada" };
// value.name
// Помилка: властивість name не відома для типу objectЯкщо структура невідома повністю, використовуйте unknown, а потім перевіряйте її. Якщо структура відома, опишіть її окремим типом.
Перевірка typeof value === "object" не гарантує, що об’єкт має потрібні властивості або правильні типи цих властивостей. Для об’єктів перевіряйте:
що значення не null;
наявність потрібних властивостей;
тип кожної властивості, яку використовуєте.
asКілька приведень типу поспіль часто приховують проблему з даними:
const user = value as User;
console.log((user.name as string).toUpperCase());Краще один раз перевірити значення type guard-функцією і далі працювати з коректним типом.
any вимикає значну частину перевірок TypeScript і може перенести помилки на етап виконання.
unknown також може містити будь-яке значення, але не дозволяє використовувати його без попередньої перевірки.
Для звуження типів застосовують typeof, instanceof, оператор in та власні type predicate-функції.
Перевірка об’єкта має враховувати null, наявність властивостей і типи цих властивостей.
as лише змінює думку компілятора і не виконує перевірку під час виконання.
Для неперевірених зовнішніх даних безпечнішим початковим типом є unknown.