Skip to main content

Що таке атрибути даних у HTML

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 зчитує їх одразу при завантаженні.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.