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

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

Дочитали статтю?