Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Необов'язковий ланцюг (?.) та нульове об'єднання (??) у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Необов'язковий ланцюг** (`?.`) повертає `undefined`, якщо будь-яка частина ланцюга є `null` або `undefined`. **Нульове об'єднання** (`??`) повертає праву частину тільки коли ліва - `null` або `undefined`, але не `0`, `""` або `false`. ```javascript user?.address?.city // undefined, без помилки user?.address?.city ?? "NYC" // "NYC" як fallback 0 ?? 10 // 0 (залишається - нуль валідний) 0 || 10 // 10 (|| замінює 0) ``` **Ключове:** використовуй `??` замість `||`, коли `0`, `false` або `""` є валідними значеннями.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Необов'язковий ланцюг** (`?.`) та **нульове об'єднання** (`??`) - два оператори ES2020, які вирішують різні задачі: `?.` зупиняє доступ до властивостей при першому `null` або `undefined`, `??` повертає запасне значення тільки коли ліва частина є `null` або `undefined`, але не коли це `0`, `""` або `false`. ## Теорія ### TL;DR - `?.` повертає `undefined` і зупиняє ланцюг при першому `null` або `undefined` - `??` повертає праву частину тільки якщо ліва - `null` або `undefined` (не інші falsy-значення) - `||` повертає праву частину для будь-якого falsy-значення, включно з `0` і `false` - зазвичай це баг - Разом: `user?.profile?.name ?? "Guest"` - безпечний доступ із запасним значенням - Правило: спочатку ланцюг через `?.`, потім значення за замовчуванням через `??` в кінці ### Швидкий приклад ```javascript const user = { name: "Alice" }; // ?. зупиняється на першому null/undefined console.log(user?.address?.city); // undefined (без помилки) console.log(user?.address?.city ?? "NYC"); // "NYC" (запасне значення) // ?? проти || - головна різниця const count = 0; console.log(count ?? 10); // 0 - нуль валідний, ?? його не чіпає console.log(count || 10); // 10 - || вважає 0 хибним і замінює ``` Останні два рядки - найпоширеніше джерело багів. Якщо `count` може бути `0`, завжди використовуй `??`. ### Ключова різниця `?.` - це захисний доступ до властивостей. `??` - це підстановка значення за замовчуванням. Вони вирішують різні проблеми і добре працюють разом. `?.` запобігає `TypeError: Cannot read properties of undefined`. `??` гарантує, що запасне значення не перезапише `0` або `false`, які `||` замінив би без жодного попередження. ### Коли використовувати - **`?.`**: доступ до вкладених об'єктів у відповідях API, виклик необов'язкових callback-ів, читання конфігурації, яка може бути не задана - **`??`**: значення за замовчуванням для налаштувань, де `0`, `""` або `false` є валідними - **`?.` і `??` разом**: `response?.data?.count ?? 0` - безпечний обхід із значущим fallback - **Залишай `||` для перевірки на truthiness**: перемикання UI-станів або трактування порожніх рядків як «відсутніх» - це задача для `||` ### Таблиця порівняння | Оператор | Повертає праву частину коли | Зберігає | Типове застосування | |----------|-----------------------------|----------|---------------------| | `?.` | Ліва - `null`/`undefined` | Повертає `undefined` при зупинці | Безпечний вкладений доступ | | `??` | Ліва - `null`/`undefined` | `0`, `""`, `false`, `NaN` | Значення за замовчуванням | | `\|\|` | Ліва - будь-яке falsy | Тільки truthy-значення | Перевірка на truthiness | | `&&` | Ліва - truthy | N/A | Умовне виконання | ### Як це працює всередині Обидва оператори входять до ES2020 і підтримуються в Node.js 14+ та всіх сучасних браузерах. Коли рушій зустрічає `obj?.prop`, він перевіряє: `obj` є `null` або `undefined`? Якщо так - повертає `undefined` і нічого більше не обчислює. Ланцюг `a?.b?.c?.d` зупиняється на першому nullish-значенні, тому жодна наступна частина не виконується. Babel транспілює обидва оператори для старих цільових середовищ через аналогічну умовну логіку. ### Типові помилки **Помилка 1: `||` замість `??`** ```javascript // Баг: timeout 0 означає "без затримки", але || замінить його const timeout = settings.timeout || 5000; // 5000, навіть якщо timeout дорівнює 0 const timeout = settings.timeout ?? 5000; // Правильно ``` **Помилка 2: зайвий `?.` після явної перевірки** ```javascript // Зайве - address вже підтверджено if (user && user.address) { const city = user.address?.city; } // Чистіше const city = user?.address?.city; ``` **Помилка 3: забутий `?.` перед необов'язковими методами** ```javascript options.onSuccess(); // TypeError, якщо onSuccess не передали options.onSuccess?.(); // undefined без помилки ``` **Помилка 4: думати, що `??` ловить усі falsy-значення** ```javascript const message = "" ?? "default"; // "" - порожній рядок не є null/undefined const message = "" || "default"; // "default" - || ловить порожні рядки ``` ### Де зустрічається в реальному коді - React: `props?.user?.avatar ?? "/default.png"` - необов'язкові пропси в компонентах - Express.js: `req?.query?.search ?? ""` - читання необов'язкових query-параметрів - Redux-селектори: `state?.auth?.user?.role ?? "guest"` - вкладений стан стору - API-відповіді: `response?.data?.items?.[0]?.id ?? null` - обхід nullable-полів - Необов'язкові callback-и: `options.onSuccess?.()` - виклик, якщо callback передали ### Питання на співбесіді **Q:** Яка різниця між `obj?.prop` і `obj && obj.prop`? **A:** Обидва запобігають помилкам, але повертають різне. `obj && obj.prop` повертає саме falsy-значення, якщо `obj` це `0` або `""`. `?.` завжди повертає `undefined`, коли ланцюг зупиняється. До того ж `?.` чіткіше сигналізує про намір. **Q:** Чому `0 ?? 10` повертає `0`, а `0 || 10` повертає `10`? **A:** Тому що `??` перевіряє тільки `null` або `undefined`, а не хибність взагалі. Нуль є falsy, але не nullish, тому `??` вважає його валідними даними і не замінює. **Q:** Чи можна використовувати optional chaining (необов'язковий ланцюг) у деструктуризації? **A:** Напряму - ні. Обхід: `const { name } = user?.profile ?? {}`. Без `?? {}` деструктуризація кинула б помилку, якщо `user?.profile` повернув `undefined`. **Q:** Що відбувається при кількох `?.` в ланцюгу `a?.b?.c?.d`? **A:** Весь ланцюг зупиняється на першому `null` або `undefined` і повертає `undefined`. Кожен `?.` незалежний: якщо `a.b` є null, ні `c`, ні `d` не обчислюються. **Q (рівень senior):** У циклі, який обробляє мільйони об'єктів, що краще: `obj?.prop ?? default` чи `obj && obj.prop || default`? **A:** Сучасні рушії оптимізують обидва варіанти приблизно однаково, а `?.` - це один оператор проти двох окремих операцій. Головна причина обирати `?.` - читабельність: він одразу показує, що тут null-safe доступ. Тягнись до даних профілювання, перш ніж щось міняти в гарячому шляху. ## Приклади ### Базовий: відповідь API в React-компоненті ```javascript function UserProfile({ apiResponse }) { const userName = apiResponse?.data?.user?.name ?? "Anonymous"; const avatarUrl = apiResponse?.data?.user?.avatar ?? "/default-avatar.png"; const isAdmin = apiResponse?.data?.user?.permissions?.isAdmin ?? false; return ( <div> <h1>Hello, {userName}</h1> <img src={avatarUrl} alt={userName} /> {isAdmin && <span>Admin</span>} </div> ); } // Працює навіть якщо apiResponse є null або дані частково відсутні ``` `??` на `isAdmin` тут важливий. `isAdmin || false` теж спрацює, але `??` точніше передає намір: підставити `false` тільки якщо значення взагалі не було задано. ### Середній рівень: необов'язкові методи та доступ до масивів ```javascript const response = { items: [ { id: 1, process: () => "processed" }, { id: 2 } // Метод process відсутній ] }; console.log(response.items?.[0]?.process?.()); // "processed" console.log(response.items?.[1]?.process?.()); // undefined - без помилки console.log(response.items?.[5]?.process?.()); // undefined - індекс не існує const result = response.items?.[1]?.process?.() ?? "default processing"; console.log(result); // "default processing" // Без optional chaining - падає response.items[1].process(); // TypeError: process is not a function ``` Синтаксис `?.()` для виклику методів легко забути. Він працює за тією ж логікою що і `?.prop`, але застосовується до виклику функції.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.