Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке атрибути даних у HTML». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Data attributes** (атрибути даних) - це HTML-атрибути з префіксом `data-`, що зберігають кастомні метадані на елементах. JavaScript читає їх через `element.dataset`. ```html <button data-product-id="42" data-category="electronics">Купити</button> ``` ```javascript button.dataset.productId // "42" (kebab-case перетворюється на camelCase) button.dataset.category // "electronics" ``` **Ключове:** Data attributes завжди є рядками. Числа і JSON потрібно перетворювати перед використанням.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Data attributes** (атрибути даних) - це HTML-атрибути з префіксом `data-`, що зберігають метадані застосунку безпосередньо на елементах. JavaScript читає їх через `element.dataset`, а HTML залишається валідним. ## Теорія ### TL;DR - Уяви стікер на фізичному предметі: ти додаєш додаткову інформацію до елемента, не змінюючи сам елемент - Префікс `data-` сигналізує браузеру, що це навмисні дані застосунку, а не порушення специфікації - `element.dataset.productId` читає `data-product-id` автоматично (kebab-case перетворюється на camelCase) - Data attributes завжди є рядками: числа і JSON потрібно перетворювати вручну перед використанням - Правило вибору: `data-*` для метаданих, які читає JavaScript; ARIA - для доступності; класи - для стилів ### Швидкий приклад ```html <button data-product-id="42" data-category="electronics"> Купити </button> <script> const button = document.querySelector("button"); console.log(button.dataset.productId); // "42" console.log(button.dataset.category); // "electronics" // Також працює через getAttribute console.log(button.getAttribute("data-product-id")); // "42" </script> ``` `dataset` - це живий об'єкт `DOMStringMap`. Зміни у `dataset` одразу оновлюють HTML-атрибут, і навпаки. ### Ключова відмінність від нестандартних атрибутів До появи `data-*` розробники писали `<div user-id="123">` або тягнули ID прямо в назви класів. Обидва підходи або давали невалідний HTML, або перетворювали код на головоломку. Префікс `data-` - це офіційний спосіб W3C додавати кастомні метадані, і браузер парсить їх у чистий об'єкт `dataset`, який зручно видно в DevTools. ### Коли використовувати - Зберігання ID для обробників подій: `<button data-user-id="123">`, потім читаємо в click handler - Делегування подій (event delegation): ідентифікатори на дочірніх елементах, зчитуємо через `e.target.closest('[data-todo-id]')` - Ініціалізація JavaScript-компонентів: `<div data-chart-type="line" data-refresh-interval="5000">` - Серверні шаблони: Rails, Django, PHP вставляють динамічні значення з бази даних у `data-*` атрибути - Селектори для тестування: `data-testid` - стандарт у Cypress і Playwright Уникай data attributes для: чутливих даних (видно в HTML-коді і DevTools), великих об'єктів (краще `<script>` з JSON), значень що оновлюються в щільних циклах (кожен запис у DOM - окрема операція). ### Як браузер це обробляє При парсингу HTML браузер створює `DOMStringMap` на DOM-вузлі кожного елемента. Це живий проксі до всіх `data-*` атрибутів. При зверненні до `element.dataset.productId` браузер конвертує camelCase назад у `product-id`, шукає `data-product-id` і повертає рядкове значення. Запис також двонаправлений: `element.dataset.newProp = 'value'` створює `data-new-prop` у DOM. ### Типові помилки **Забути, що data attributes завжди є рядками** ```javascript // НЕПРАВИЛЬНО: покладається на неявне приведення типів у JS const button = document.querySelector('[data-count]'); button.dataset.count = '5'; if (button.dataset.count > 3) { // працює випадково console.log('Багато'); } // ПРАВИЛЬНО: явне перетворення типу const count = parseInt(button.dataset.count, 10); if (count > 3) { console.log('Багато'); } ``` **Зберігати JSON без серіалізації** ```javascript // НЕПРАВИЛЬНО: збережеться "[object Object]" element.dataset.config = { theme: 'dark', lang: 'en' }; // ПРАВИЛЬНО: stringify перед записом, parse при читанні element.dataset.config = JSON.stringify({ theme: 'dark', lang: 'en' }); const config = JSON.parse(element.dataset.config); console.log(config.theme); // "dark" ``` **Оновлювати data attributes у щільному циклі** ```javascript // НЕПРАВИЛЬНО: 1000 записів у DOM for (let i = 0; i < 1000; i++) { element.dataset.score = i; } // ПРАВИЛЬНО: обчислення в JS, один запис у DOM наприкінці let score = 0; for (let i = 0; i < 1000; i++) { score = i; } element.dataset.score = score; ``` **Зберігати чутливі дані** ```html <!-- НЕПРАВИЛЬНО: видно всім хто відкриє DevTools --> <div data-api-key="sk-1234567890abcdef"></div> <!-- ПРАВИЛЬНО: отримуй секрети через захищений endpoint --> <script> const apiKey = await fetch('/api/get-key').then(r => r.json()); </script> ``` ### Де зустрічається на практиці - React: `data-testid` для селекторів у Cypress і Playwright - jQuery-плагіни: `$('[data-toggle="modal"]')` для ініціалізації компонентів - Аналітика: бібліотеки читають `data-event-*` атрибути для трекінгу кліків - Web Components: передача початкової конфігурації до завантаження JavaScript - CSS: `[data-priority="high"] { color: red; }` для стилів на основі атрибутів ### Питання на співбесіді **Q:** Чим data attributes відрізняються від зберігання даних у JS-властивостях елемента? **A:** Data attributes зберігаються в HTML і не зникають при `cloneNode()` та серверній серіалізації. JS-властивості - зникають. Також data attributes видно в DevTools і можна задати з серверних шаблонів. Для метаданих, що є частиною визначення елемента, обирай `data-*`; для стану виконання - JS-властивості. **Q:** Чи можна використовувати data attributes у CSS? **A:** Так. Селектори атрибутів працюють: `[data-priority="high"] { color: red; }`. Значення можна читати в псевдоелементах через `content: attr(data-label)`. Але CSS може тільки перевіряти наявність або точне значення, не обчислювати. Для динамічної стилізації на основі даних що змінюються, краще додавати і видаляти класи через JavaScript. **Q:** Що відбувається з великими літерами в назвах data атрибутів? **A:** HTML-атрибути регістронезалежні, тому `data-ProductID` стає `data-productid` у DOM. Використовуй тільки малі літери і дефіси. API `dataset` конвертує дефіси в camelCase: `data-product-id` стає `dataset.productId`. **Q:** (Senior) Наскільки повільний доступ через `dataset` порівняно з кешованою JS-змінною? **A:** Кожне звернення до `dataset` виконує пошук атрибута, що трохи повільніше за читання локальної змінної. Для значень до яких звертаєшся часто, кешуй один раз: `const id = element.dataset.id;`, далі використовуй `id`. Для великих об'єктів зберігай у атрибуті ключ посилання, а сам об'єкт тримай у JavaScript `Map`. ## Приклади ### Делегування подій з data attributes Один слухач на батьківському елементі замість окремих слухачів на кожному дочірньому. Data attributes дають кожному дочірньому елементу унікальну ідентичність без додаткових JavaScript-об'єктів: ```html <ul id="todo-list"> <li data-todo-id="1" data-priority="high"> <span>Виправити баг в авторизації</span> <button class="delete-btn">Видалити</button> </li> <li data-todo-id="2" data-priority="low"> <span>Оновити документацію</span> <button class="delete-btn">Видалити</button> </li> </ul> <script> document.getElementById('todo-list').addEventListener('click', (e) => { // Йдемо вгору по DOM до найближчого li з data-todo-id const todoItem = e.target.closest('[data-todo-id]'); if (!todoItem) return; const todoId = todoItem.dataset.todoId; const priority = todoItem.dataset.priority; if (e.target.classList.contains('delete-btn')) { console.log(`Видаляємо todo ${todoId} (пріоритет: ${priority})`); // Виведе: "Видаляємо todo 1 (пріоритет: high)" todoItem.remove(); } }); </script> ``` `e.target.closest('[data-todo-id]')` йде вгору по DOM від елемента на якому відбувся клік до першого відповідного предка. Це коректно обробляє кліки по вкладених `<span>` або `<button>` всередині `<li>`. ### Передача серверних даних у JavaScript Серверні шаблони можуть вбудовувати динамічні значення в data attributes. JavaScript зчитує їх при завантаженні сторінки без додаткового API-запиту: ```html <!-- Сервер рендерить це з реальними значеннями з бази даних --> <div id="user-profile" data-user-id="99" data-role="admin" data-locale="uk-UA" > Вітаємо, Олексій </div> <script> const profile = document.getElementById('user-profile'); const userId = profile.dataset.userId; // "99" const role = profile.dataset.role; // "admin" const locale = profile.dataset.locale; // "uk-UA" if (role === 'admin') { console.log(`Адмін ${userId}, локаль: ${locale}`); // Виведе: "Адмін 99, локаль: uk-UA" } </script> ``` Для невеликих конфігурацій окремий JSON-endpoint не потрібен. Сервер записує значення, JavaScript зчитує їх одразу при завантаженні.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.