Web Components — Custom Elements и Shadow DOM
Web Components - состав стандарта
Web Components — набор браузерных стандартов для переиспользуемых UI-блоков без обязательного React/Vue:
- Custom Elements — свои теги (
<user-card>). - Shadow DOM — изолированное поддерево DOM и стили внутри компонента.
- HTML templates —
<template>для клонирования разметки. - Slots — точки вставки внешнего контента в шаблон.
Фреймворки (React, Vue, Angular) часто используют Shadow DOM выборочно или эмулируют инкапсуляцию по-своему. Нативные компоненты уместны в дизайн-системах, embed-виджетах и постепенной модернизации статичных страниц.
Предварительно: классы, работа с DOM, события.
Когда выбирать Web Components
Имеет смысл, если один UI-блок нужен в разных стеках — статический сайт, SPA, CMS, embed у партнёра. Нативные Custom Elements + Shadow DOM дают долгоживущий контракт без привязки к React/Vue; фреймворки при этом могут использовать Shadow DOM выборочно или эмулировать инкапсуляцию своим способом.
Custom Elements
Класс наследует HTMLElement, регистрируется один раз:
Код ITЗагрузка примера кода…
Разбор:
- В этом фрагменте используется конструкция
class UserCard extends HTMLElement {как точка входа сценария. - Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
- Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
- Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
| Хук | Когда вызывается |
|---|---|
connectedCallback | элемент добавлен в документ |
disconnectedCallback | удалён из документа |
attributeChangedCallback | изменился атрибут из observedAttributes |
adoptedCallback | элемент перенесли в другой документ (редко) |
Использование в HTML:
<user-card name="Анна">
<p>Менеджер проекта</p>
</user-card>
Разбор:
- В этом фрагменте используется конструкция
<user-card name="Анна">как точка входа сценария. - Разметка задаёт опорные элементы интерфейса, к которым затем подключается JavaScript-логика.
- Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
- Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
Имена custom elements обязаны содержать дефис (user-card), чтобы не пересечься со встроенными тегами.
Shadow DOM
attachShadow() создаёт отдельное дерево, стили снаружи не проникают внутрь (и наоборот — с оговорками для CSS-переменных).
Код ITЗагрузка примера кода…
Разбор:
- В этом фрагменте используется конструкция
class StatusBadge extends HTMLElement {как точка входа сценария. - Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
- Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
- Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
mode | Поведение |
|---|---|
open | element.shadowRoot доступен снаружи |
closed | shadowRoot снаружи null (как у встроенного <video>) |
:host — стили самого хост-элемента. :host([active]) — при атрибуте active.
Слоты
<slot name="..."> — место, куда попадает разметка светлого DOM (дети custom element):
<article-panel>
<h2 slot="title">Новости</h2>
<p>Текст блока</p>
</article-panel>
Разбор:
- В этом фрагменте используется конструкция
<article-panel>как точка входа сценария. - Разметка задаёт опорные элементы интерфейса, к которым затем подключается JavaScript-логика.
- Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
- Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
// в shadow:
// <header><slot name="title"></slot></header>
// <div class="body"><slot></slot></div> <!-- default slot -->
Разбор:
- В этом фрагменте используется конструкция
// в shadow:как точка входа сценария. - Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
- Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
- Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
События со слотов всплывают с пометкой, что прошли через shadow boundary — учитывайте при делегировании.
Шаблон template
Разметку удобно хранить в <template> и клонировать:
<template id="todo-item-tpl">
<li><button type="button" class="done">✓</button> <span class="text"></span></li>
</template>
Разбор:
- В этом фрагменте используется конструкция
<template id="todo-item-tpl">как точка входа сценария. - Разметка задаёт опорные элементы интерфейса, к которым затем подключается JavaScript-логика.
- Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
- Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
const tpl = document.getElementById('todo-item-tpl');
const node = tpl.content.cloneNode(true);
node.querySelector('.text').textContent = 'Купить молоко';
list.appendChild(node);
Разбор:
- В этом фрагменте используется конструкция
const tpl = document.getElementById('todo-item-tpl');как точка входа сценария. - Код показывает последовательность действий: получение данных, проверка условий и выполнение целевого действия.
- Поиск элементов через
querySelectorявно привязывает сценарий к нужным узлам DOM. - Ключевые вызовы и свойства опираются на стандартные API JavaScript/браузера, поэтому шаблон легко перенести в реальный проект.
- Такой пример удобно расширять обработкой ошибок, логированием и дополнительной валидацией входных данных.
В связке с 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, команда на React | React; web components для изолированных legacy-блоков |
| Дизайн-система для нескольких стеков | Stencil/Lit → web components |
| Простой сайт без сборки | Ванильный JS (виджеты) может быть проще |
Минусы нативных компонентов — нет встроенного виртуального DOM, сложнее тестировать без браузера, SSR требует отдельных решений.
Практический маршрут внедрения
Если команда только начинает использовать Web Components, удобен поэтапный план:
- Выделить один изолированный UI-блок (badge, card, date-picker).
- Описать публичный API компонента через атрибуты, свойства и кастомные события.
- Сделать shadow-стили и проверить совместимость в текущей теме сайта.
- Подключить компонент в 2-3 разных страницах или сервисах.
- Зафиксировать контракт в документации дизайн-системы.
Так формируется стабильная библиотека компонентов, которую используют проекты на разных стеках.
Частые ошибки интеграции
- Компонент напрямую манипулирует глобальным CSS и ломает внешние страницы.
- Внутренние DOM-узлы возвращаются наружу как часть API, и инкапсуляция теряет смысл.
- События не помечаются
bubbles: true, из-за чего родительские контейнеры их не получают. - Логика рендера выполняется при каждом
attributeChangedCallbackбез проверки изменений, что создаёт лишние перерисовки.
Для сценариев с формами и сетью сочетайте web components с валидацией и отменой запросов.
Краткий итог
Custom Elements дают свой тег и жизненный цикл. Shadow DOM изолирует разметку и CSS. Slots подключают внешний контент. Стандарты дополняют, а не обязаны заменять фреймворки — но полезны там, где нужен один переиспользуемый блок в любом окружении.