Пошук уроків, статей та іншого контенту
Безпечно працюйте зі значеннями unknown, звужуючи їх перед доступом до властивостей і викликом методів.
unknownunknown описує значення, тип якого нам невідомий. На відміну від any, TypeScript не дозволяє виконувати над unknown довільні операції без попередньої перевірки.
function printLength(value: unknown): void {
// Помилка: властивість length недоступна для unknown
// console.log(value.length);
if (typeof value === "string" || Array.isArray(value)) {
console.log(value.length);
}
}
printLength("TypeScript");
printLength([1, 2, 3]);Значення unknown можна присвоїти змінній будь-якого типу після перевірки, але не навпаки:
let value: unknown = "hello";
if (typeof value === "string") {
const text: string = value;
console.log(text.toUpperCase());
}unknown особливо корисний для:
даних із зовнішнього API;
результатів JSON.parse;
значень, отриманих від користувача;
параметрів функцій, які приймають різні типи;
обробки помилок у catch.
typeofПеревірка typeof звужує тип значення в межах відповідної гілки.
function describe(value: unknown): string {
if (typeof value === "string") {
return `Рядок: ${value.toUpperCase()}`;
}
if (typeof value === "number") {
return `Число: ${value.toFixed(2)}`;
}
if (typeof value === "boolean") {
return `Булеве значення: ${value ? "так" : "ні"}`;
}
if (typeof value === "undefined") {
return "Значення не визначене";
}
if (typeof value === "bigint") {
return `BigInt: ${value.toString()}`;
}
if (typeof value === "symbol") {
return `Symbol: ${value.toString()}`;
}
if (typeof value === "function") {
return "Функція";
}
return "Об'єкт або null";
}
console.log(describe("typescript"));
console.log(describe(12.5));
console.log(describe(true));
console.log(describe(undefined));Перевірка typeof value === "object" має особливість: вона підходить і для звичайних об'єктів, і для null.
function getObjectKeys(value: unknown): string[] {
if (typeof value === "object" && value !== null) {
return Object.keys(value);
}
return [];
}
console.log(getObjectKeys({ name: "Anna", age: 25 }));
console.log(getObjectKeys(null));Тому для безпечної роботи з об'єктом зазвичай використовують обидві перевірки:
typeof value === "object" && value !== nullДля масивів слід використовувати Array.isArray. Вона не лише перевіряє значення під час виконання, а й звужує його тип у TypeScript.
function sumNumbers(value: unknown): number {
if (!Array.isArray(value)) {
return 0;
}
let sum = 0;
for (const item of value) {
if (typeof item === "number") {
sum += item;
}
}
return sum;
}
console.log(sumNumbers([1, 2, 3]));
console.log(sumNumbers([1, "2", 3, null]));
console.log(sumNumbers("123"));Після Array.isArray(value) TypeScript знає, що value є масивом, але його елементи все ще можуть мати невідомий тип. Якщо потрібно викликати метод або виконати операцію над елементами, кожен елемент також треба звузити.
inОператор in перевіряє наявність властивості в об'єкті. Перед його використанням потрібно переконатися, що значення є об'єктом і не є null.
function hasMessage(value: unknown): boolean {
if (typeof value !== "object" || value === null) {
return false;
}
return "message" in value;
}
console.log(hasMessage({ message: "Помилка" }));
console.log(hasMessage({ status: 500 }));
console.log(hasMessage(null));Наявність властивості ще не гарантує її тип. Наприклад, властивість message може містити число, масив або інший об'єкт. Тому після перевірки властивості потрібно перевірити і її значення.
function getMessage(value: unknown): string | undefined {
if (typeof value !== "object" || value === null || !("message" in value)) {
return undefined;
}
const message = value.message;
if (typeof message === "string") {
return message;
}
return undefined;
}
console.log(getMessage({ message: "Не вдалося зберегти дані" }));
console.log(getMessage({ message: 500 }));
console.log(getMessage({ status: 500 }));instanceofinstanceof перевіряє, чи створене значення за допомогою певного класу. Це зручно для стандартних об'єктів, наприклад Date або Error.
function formatDate(value: unknown): string {
if (value instanceof Date) {
return value.toISOString();
}
return "Значення не є датою";
}
function getErrorMessage(value: unknown): string {
if (value instanceof Error) {
return value.message;
}
return "Невідома помилка";
}
console.log(formatDate(new Date("2025-01-15T10:00:00Z")));
console.log(getErrorMessage(new Error("Файл не знайдено")));
console.log(getErrorMessage("Помилка у вигляді рядка"));Після перевірки value instanceof Error TypeScript дозволяє безпечно звернутися до value.message.
Якщо одна й та сама перевірка використовується в кількох місцях, її можна винести в окрему функцію-предикат.
Функція-предикат має спеціальний тип результату:
value is SomeTypeЦе повідомляє TypeScript: якщо функція повернула true, значення value має тип SomeType.
interface User {
id: number;
name: string;
email: string;
}
function isUser(value: unknown): value is User {
if (typeof value !== "object" || value === null) {
return false;
}
if (!("id" in value) || !("name" in value) || !("email" in value)) {
return false;
}
const user = value as Record<string, unknown>;
return (
typeof user.id === "number" &&
Number.isInteger(user.id) &&
typeof user.name === "string" &&
typeof user.email === "string"
);
}
function getUserLabel(value: unknown): string {
if (!isUser(value)) {
return "Некоректні дані користувача";
}
return `${value.name} <${value.email}>`;
}
console.log(
getUserLabel({
id: 1,
name: "Олена",
email: "olena@example.com",
}),
);
console.log(
getUserLabel({
id: "1",
name: "Олена",
email: "olena@example.com",
}),
);Після виклику isUser(value) у позитивній гілці TypeScript сприймає value як User. Тому доступ до value.name та value.email є безпечним.
Важливо, що TypeScript не перевіряє реалізацію предиката на повну відповідність оголошеному типу. Відповідальність за правильність перевірки залишається на розробнику.
Дані з JSON.parse або зовнішнього джерела варто розглядати як unknown, доки вони не пройдуть перевірку.
interface Product {
id: number;
title: string;
price: number;
}
function isProduct(value: unknown): value is Product {
if (typeof value !== "object" || value === null) {
return false;
}
if (!("id" in value) || !("title" in value) || !("price" in value)) {
return false;
}
const product = value as Record<string, unknown>;
return (
typeof product.id === "number" &&
typeof product.title === "string" &&
typeof product.price === "number" &&
Number.isFinite(product.price)
);
}
function parseProduct(json: string): Product | undefined {
const data: unknown = JSON.parse(json);
if (isProduct(data)) {
return data;
}
return undefined;
}
const product = parseProduct(
'{"id": 10, "title": "Клавіатура", "price": 2499.99}',
);
if (product !== undefined) {
console.log(`${product.title}: ${product.price} грн`);
}Оголошення const data: unknown змушує явно виконати перевірку перед доступом до властивостей. Без такої перевірки помилки у форматі вхідних даних могли б виникнути вже під час виконання програми.
unknown у блоці catchПомилкою може бути не лише екземпляр Error. У JavaScript можна викинути рядок, число або будь-яке інше значення. Тому значення помилки потрібно перевіряти перед доступом до message.
function getErrorText(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(getErrorText(error));
}Увімкнений параметр useUnknownInCatchVariables робить змінну catch типу unknown. Це захищає від припущення, що будь-яка помилка обов'язково має властивість message.
Оператор as лише повідомляє TypeScript, що розробник вважає значення певного типу. Він не перевіряє дані під час виконання.
const value: unknown = "hello";
const text = value as string;
console.log(text.toUpperCase());Цей приклад безпечний, бо фактичне значення є рядком. Але таке приведення може приховати помилку:
const value: unknown = 123;
const text = value as string;
// Помилка під час виконання: value.toUpperCase не є функцією
// console.log(text.toUpperCase());Для зовнішніх даних краще використовувати перевірку або користувацький type guard. as доречний лише тоді, коли тип уже гарантований іншою логікою, яку TypeScript не може побачити.
function printName(value: unknown): void {
// Помилка компіляції:
// console.log(value.name);
}Потрібно спочатку перевірити, що значення є об'єктом, і що властивість має потрібний тип.
unknown та anyany вимикає перевірку типів:
let unsafe: any = 123;
unsafe.toUpperCase(); // TypeScript не повідомить про проблемуunknown змушує перевіряти значення:
let safe: unknown = 123;
// Помилка компіляції:
// safe.toUpperCase();
if (typeof safe === "string") {
console.log(safe.toUpperCase());
}Для невідомих даних перевагу слід надавати unknown, а не any.
typeof value === "object"Така перевірка пропускає null:
function process(value: unknown): void {
if (typeof value === "object") {
// value може бути null
// Доступ до властивостей без додаткової перевірки небезпечний
}
}Використовуйте:
if (typeof value === "object" && value !== null) {
// Тут значення є не-null об'єктом
}Наявність email не означає, що це рядок:
const data: unknown = {
email: 123,
};Безпечний код перевіряє і властивість, і її значення:
if (
typeof data === "object" &&
data !== null &&
"email" in data &&
typeof data.email === "string"
) {
console.log(data.email.toLowerCase());
}unknown використовується для значень, тип яких невідомий.
На відміну від any, unknown не дозволяє звертатися до властивостей або викликати методи без перевірки.
Для звуження застосовують typeof, instanceof, Array.isArray та оператор in.
Перевірка typeof value === "object" повинна враховувати, що null також має тип "object".
Користувацькі type guards із типом value is Type дають змогу повторно використовувати складні перевірки.
Оператор as не перевіряє значення під час виконання, тому він не замінює валідацію.
Дані ззовні програми потрібно звужувати перед доступом до їхніх властивостей і викликом методів.