Перейти к основному содержимому

Web Components — Custom Elements и Shadow DOM

Разработчику Архитектору

Web Components - состав стандарта

Web Components — набор браузерных стандартов для переиспользуемых UI-блоков без обязательного React/Vue:

  1. Custom Elements — свои теги (<user-card>).
  2. Shadow DOM — изолированное поддерево DOM и стили внутри компонента.
  3. HTML templates<template> для клонирования разметки.
  4. Slots — точки вставки внешнего контента в шаблон.

Фреймворки (React, Vue, Angular) часто используют Shadow DOM выборочно или эмулируют инкапсуляцию по-своему. Нативные компоненты уместны в дизайн-системах, embed-виджетах и постепенной модернизации статичных страниц.

Предварительно: классы, работа с DOM, события.


Custom Elements

Класс наследует HTMLElement, регистрируется один раз:

class UserCard extends HTMLElement {
connectedCallback() {
if (this._ready) return;
this._ready = true;

const name = this.getAttribute('name') ?? 'Гость';
this.innerHTML = `
<article class="user-card">
<h2>${escapeHtml(name)}</h2>
<slot></slot>
</article>
`;
}

static get observedAttributes() {
return ['name'];
}

attributeChangedCallback(attr, oldVal, newVal) {
if (attr === 'name' && oldVal !== newVal) {
this._ready = false;
this.connectedCallback();
}
}
}

function escapeHtml(text) {
return text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;');
}

customElements.define('user-card', UserCard);
ХукКогда вызывается
connectedCallbackэлемент добавлен в документ
disconnectedCallbackудалён из документа
attributeChangedCallbackизменился атрибут из observedAttributes
adoptedCallbackэлемент перенесли в другой документ (редко)

Использование в HTML:

<user-card name="Анна">
<p>Менеджер проекта</p>
</user-card>

Имена custom elements обязаны содержать дефис (user-card), чтобы не пересечься со встроенными тегами.


Shadow DOM

attachShadow() создаёт отдельное дерево, стили снаружи не проникают внутрь (и наоборот — с оговорками для CSS-переменных).

class StatusBadge extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: 'open' });

shadow.innerHTML = `
<style>
:host {
display: inline-block;
font: 14px/1.4 system-ui, sans-serif;
}
.badge {
padding: 2px 8px;
border-radius: 999px;
background: var(--badge-bg, #e0e0e0);
}
</style>
<span class="badge"><slot></slot></span>
`;
}
}

customElements.define('status-badge', StatusBadge);
modeПоведение
openelement.shadowRoot доступен снаружи
closedshadowRoot снаружи null (как у встроенного <video>)

:host — стили самого хост-элемента. :host([active]) — при атрибуте active.


Слоты

<slot name="..."> — место, куда попадает разметка светлого DOM (дети custom element):

<article-panel>
<h2 slot="title">Новости</h2>
<p>Текст блока</p>
</article-panel>
// в shadow:
// <header><slot name="title"></slot></header>
// <div class="body"><slot></slot></div> <!-- default slot -->

События со слотов всплывают с пометкой, что прошли через shadow boundary — учитывайте при делегировании.


Шаблон template

Разметку удобно хранить в <template> и клонировать:

<template id="todo-item-tpl">
<li><button type="button" class="done"></button> <span class="text"></span></li>
</template>
const tpl = document.getElementById('todo-item-tpl');
const node = tpl.content.cloneNode(true);
node.querySelector('.text').textContent = 'Купить молоко';
list.appendChild(node);

В связке с Shadow DOM шаблон кладут внутрь shadow root при инициализации.


События и публичный API

  • Внутри класса вызывайте this.dispatchEvent(new CustomEvent('save', { detail: { id: 1 }, bubbles: true })) — слушатели на странице поймают событие при bubbles: true.
  • Публичные методы на классе (show(), reset()) вызывают снаружи: document.querySelector('user-card').focus().

Не экспонируйте внутренние узлы shadow root в продакшене — это ломает инкапсуляцию.


Когда брать Web Components, когда фреймворк

СитуацияПодход
Виджет на чужой CMS, один тег на страницеCustom Element + Shadow
Большое SPA, команда на ReactReact; web components для изолированных legacy-блоков
Дизайн-система для нескольких стековStencil/Lit → web components
Простой сайт без сборкиВанильный JS (виджеты) может быть проще

Минусы нативных компонентов: нет встроенного виртуального DOM, сложнее тестировать без браузера, SSR требует отдельных решений.


Краткий итог

Custom Elements дают свой тег и жизненный цикл. Shadow DOM изолирует разметку и CSS. Slots подключают внешний контент. Стандарты дополняют, а не обязаны заменять фреймворки — но полезны там, где нужен один переиспользуемый блок в любом окружении.


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).