Пошук уроків, статей та іншого контенту
Дізнайтеся, як безпечно звертатися до вкладених властивостей, методів та елементів за допомогою ?.. Навчіться уникати помилок при роботі з потенційно відсутніми значеннями.
Optional chaining — це оператор ?., який дає змогу безпечно звертатися до властивостей, методів або елементів, що можуть бути відсутніми.
Без optional chaining звернення до властивості вкладеного об’єкта може спричинити помилку:
const user = {};
console.log(user.profile.name);
// TypeError: Cannot read properties of undefinedВластивість profile відсутня, тому її значення дорівнює undefined. JavaScript не може прочитати name у значення undefined.
За допомогою ?. цей код не завершується помилкою:
const user = {};
console.log(user.profile?.name);
// undefinedЯкщо значення ліворуч від ?. дорівнює null або undefined, весь вираз повертає undefined.
Звичайний запис:
object.propertyЗапис із optional chaining:
object?.propertyПриклад:
const user = {
name: "Олена",
address: {
city: "Львів"
}
};
console.log(user?.name); // "Олена"
console.log(user?.address?.city); // "Львів"
console.log(user?.contact?.phone); // undefinedУ виразі user?.contact?.phone:
JavaScript перевіряє user.
Потім перевіряє contact.
Оскільки contact відсутня, повертається undefined.
Помилка не виникає.
Optional chaining зупиняє виконання, якщо значення дорівнює:
null;
undefined.
Інші falsy-значення не зупиняють ланцюжок:
const data = {
count: 0,
active: false,
title: ""
};
console.log(data?.count); // 0
console.log(data?.active); // false
console.log(data?.title); // ""Це відрізняється від перевірки через &&, де значення 0, false або "" можуть вплинути на результат.
Optional chaining особливо корисний для об’єктів, які можуть мати неповну структуру. Наприклад, дані від сервера можуть відрізнятися для різних користувачів.
function getUserCity(user) {
return user?.profile?.address?.city;
}
console.log(
getUserCity({
profile: {
address: {
city: "Київ"
}
}
})
); // "Київ"
console.log(
getUserCity({
profile: {}
})
); // undefined
console.log(getUserCity(null)); // undefinedВажливо використовувати ?. на кожному потенційно відсутньому рівні:
const user = {};
console.log(user?.profile?.address?.city);Якщо написати лише:
console.log(user?.profile.address.city);помилки все одно не уникнути. user?.profile поверне undefined, але після цього JavaScript спробує виконати undefined.address.
Оператор ?. можна використовувати перед квадратними дужками:
const users = [
{ name: "Олена" },
{ name: "Андрій" }
];
console.log(users?.[0]?.name); // "Олена"
console.log(users?.[1]?.name); // "Андрій"
console.log(users?.[5]?.name); // undefinedЦе корисно, коли сам масив може бути відсутнім:
function getFirstProductName(products) {
return products?.[0]?.name;
}
console.log(
getFirstProductName([
{ name: "Клавіатура" }
])
); // "Клавіатура"
console.log(getFirstProductName([])); // undefined
console.log(getFirstProductName(null)); // undefinedКвадратні дужки також дають змогу використовувати змінну як ключ:
const settings = {
theme: "dark",
language: "uk"
};
const key = "language";
console.log(settings?.[key]); // "uk"
console.log(settings?.["timezone"]); // undefinedOptional chaining можна застосовувати для виклику методу:
const user = {
sayHello() {
return "Привіт!";
}
};
console.log(user.sayHello?.()); // "Привіт!"Якщо методу немає, виклик не спричинить помилку:
const user = {};
console.log(user.sayHello?.()); // undefinedЦе корисно, коли функція є необов’язковою, наприклад callback:
function processData(data, onSuccess) {
const result = data.toUpperCase();
onSuccess?.(result);
}
processData("javascript", (result) => {
console.log(result); // JAVASCRIPT
});
processData("typescript");
// Помилки немає, callback просто не викликаєтьсяЗапис:
onSuccess?.(result);означає: викликати onSuccess, лише якщо це значення не є null або undefined.
Вираз object.method?.() перевіряє, чи існує метод. Але якщо властивість існує і містить не функцію, виникне помилка:
const object = {
value: "це не функція"
};
object.value?.();
// TypeError: object.value is not a functionOptional chaining перевіряє відсутність значення, але не перетворює довільне значення на функцію.
Optional chaining часто використовують разом з оператором ??.
Оператор ?? повертає значення праворуч, якщо значення ліворуч дорівнює null або undefined:
const user = {};
const displayName = user.profile?.name ?? "Невідомий користувач";
console.log(displayName); // "Невідомий користувач"Це відрізняється від оператора ||.
const settings = {
fontSize: 0
};
console.log(settings.fontSize || 16); // 16
console.log(settings.fontSize ?? 16); // 0|| вважає 0, false та порожній рядок відсутніми значеннями. ?? замінює лише null та undefined.
Приклад із даними користувача:
function getProfileInfo(user) {
return {
name: user?.profile?.name ?? "Без імені",
city: user?.profile?.address?.city ?? "Місто не вказано",
avatar: user?.profile?.avatarUrl ?? "/default-avatar.png"
};
}
console.log(
getProfileInfo({
profile: {
name: "Марія",
address: {
city: "Одеса"
}
}
})
);
console.log(getProfileInfo(null));Якщо optional chaining зупиняє ланцюжок, вирази праворуч не обчислюються:
let index = 0;
const result = null?.[index++];
console.log(result); // undefined
console.log(index); // 0index++ не виконався, оскільки JavaScript вже знав, що результатом буде undefined.
Це може бути корисно, але не варто покладатися на побічні ефекти всередині таких виразів. Краще писати код так, щоб його поведінка була очевидною.
Optional chaining працює безперервним ланцюжком. Якщо розірвати ланцюжок дужками, захист більше не поширюється:
const user = null;
console.log(user?.profile?.name); // undefined
console.log((user?.profile).name); // TypeErrorУ другому прикладі:
user?.profile повертає undefined;
дужки завершують optional chaining;
.name виконується вже для undefined.
Якщо потрібен безпечний доступ, не розривайте ланцюжок без необхідності:
console.log(user?.profile?.name); // undefinedОператор ?. не допомагає, якщо сама змінна не оголошена:
console.log(unknownUser?.name);
// ReferenceError: unknownUser is not definedJavaScript спочатку має знайти змінну unknownUser, але такої змінної не існує.
Якщо змінна оголошена та містить undefined, optional chaining працює:
let currentUser;
console.log(currentUser?.name); // undefinedТому optional chaining призначений для роботи з відсутніми властивостями значення, а не з неоголошеними ідентифікаторами.
Не можна записувати значення через optional chaining:
const user = {};
// Помилка синтаксису:
// user?.name = "Олена";Ліва частина присвоєння має бути конкретною властивістю або змінною. Оператор ?. призначений для безпечного читання або виклику, а не для зміни значень.
Замість цього потрібно явно перевірити об’єкт:
const user = {};
if (user) {
user.name = "Олена";
}Або заздалегідь створити потрібну структуру:
const user = {
profile: {}
};
user.profile.name = "Олена";Розглянемо функцію, яка формує короткий опис товару. Дані можуть містити відсутні поля:
function describeProduct(product) {
const name = product?.name ?? "Без назви";
const category = product?.category?.title ?? "Без категорії";
const firstTag = product?.tags?.[0] ?? "Без тегів";
const discount = product?.pricing?.discount;
const discountText =
discount != null ? `Знижка: ${discount}%` : "Без знижки";
return `${name} — ${category}. ${firstTag}. ${discountText}`;
}
const completeProduct = {
name: "Ноутбук",
category: {
title: "Електроніка"
},
tags: ["робота", "навчання"],
pricing: {
discount: 10
}
};
const incompleteProduct = {
name: "Мишка"
};
console.log(describeProduct(completeProduct));
// Ноутбук — Електроніка. робота. Знижка: 10%
console.log(describeProduct(incompleteProduct));
// Мишка — Без категорії. Без тегів. Без знижки
console.log(describeProduct(null));
// Без назви — Без категорії. Без тегів. Без знижкиУ цьому прикладі:
product?.name безпечно читає назву;
product?.category?.title перевіряє кілька рівнів;
product?.tags?.[0] безпечно читає перший елемент масиву;
product?.pricing?.discount не викликає помилки, якщо ціна відсутня;
?? підставляє значення за замовчуванням.
Optional chaining зручний, але надмірне його використання може приховати помилки в даних.
Наприклад:
const result = response?.data?.user?.profile?.name;Якщо name не відображається, незрозуміло, яка саме частина структури відсутня. У критично важливому коді іноді краще явно перевірити дані та повідомити про некоректну відповідь.
function getRequiredUserName(response) {
if (!response || !response.data || !response.data.user) {
throw new Error("Відповідь не містить даних користувача");
}
return response.data.user.name;
}Використовуйте ?., коли відсутність значення є нормальною ситуацією:
необов’язкове поле;
відсутній профіль;
необов’язковий callback;
елемент масиву, якого може не бути;
дані, які завантажуються асинхронно.
Якщо значення обов’язково має існувати, краще перевірити це явно.
const user = {};
user?.profile.name;
// ПомилкаПравильно:
user?.profile?.name;||const config = {
retries: 0
};
const retries = config.retries || 3;
console.log(retries); // 3Якщо 0 є допустимим значенням, використовуйте ??:
const retries = config.retries ?? 3;
console.log(retries); // 0const object = {
run: "готово"
};
object.run?.();
// TypeError?.() захищає від null та undefined, але не від неправильного типу.
notDeclared?.value;
// ReferenceErrorСпочатку потрібно оголосити змінну:
const notDeclared = undefined;
console.log(notDeclared?.value); // undefinedconst data = null;
(data?.user).name;
// TypeErrorПравильно:
data?.user?.name;// Синтаксично некоректно:
// object?.property = value;Для присвоєння потрібна явна перевірка об’єкта.
Напишіть функцію getOrderSummary(order), яка повертає рядок такого формату:
Замовлення: Книга, кількість: 2, місто: ХарківФункція має безпечно працювати з такими даними:
const order = {
items: [
{
product: {
name: "Книга"
},
quantity: 2
}
],
customer: {
address: {
city: "Харків"
}
}
};Якщо товар, кількість або місто відсутні, використовуйте значення:
"Невідомий товар";
1;
"Місто не вказано".
Один із можливих варіантів розв’язання:
function getOrderSummary(order) {
const productName =
order?.items?.[0]?.product?.name ?? "Невідомий товар";
const quantity = order?.items?.[0]?.quantity ?? 1;
const city =
order?.customer?.address?.city ?? "Місто не вказано";
return `Замовлення: ${productName}, кількість: ${quantity}, місто: ${city}`;
}
console.log(
getOrderSummary({
items: [
{
product: {
name: "Книга"
},
quantity: 2
}
],
customer: {
address: {
city: "Харків"
}
}
})
);
console.log(getOrderSummary({}));
// Замовлення: Невідомий товар, кількість: 1, місто: Місто не вказано?. безпечно читає властивості, якщо об’єкт може бути null або undefined.
Оператор можна використовувати для:
вкладених властивостей;
елементів масивів;
властивостей із динамічними ключами;
необов’язкових методів і callback-функцій.
?. повертає undefined, якщо значення ліворуч дорівнює null або undefined.
Для значень за замовчуванням зручно поєднувати ?. з ??.
Optional chaining не захищає від:
неоголошених змінних;
виклику значення, яке не є функцією;
помилок після розриву ланцюжка дужками.
Оператор не можна використовувати в лівій частині присвоєння.
Не використовуйте ?. там, де відсутність даних означає помилку, яку потрібно явно обробити.