lenec ru

← все посты

Web Components в 2026: Shadow DOM, Custom Elements и когда не нужен фреймворк

11K

Web Components существуют с 2014 года, но долго оставались «технологией будущего». В 2026 году ситуация изменилась: все браузеры поддерживают Custom Elements v1, Shadow DOM, Declarative Shadow DOM работает без JS, а Lit сделал DX сопоставимым с React. Разбираемся, когда Web Components — правильный выбор, а когда проще взять фреймворк.

Custom Elements v1: define, lifecycle, attributes

Custom Elements позволяют создавать собственные HTML-теги с инкапсулированной логикой:

class UserCard extends HTMLElement {
  static observedAttributes = ['name', 'avatar'];

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback(name, oldVal, newVal) {
    if (oldVal !== newVal) this.render();
  }

  render() {
    this.shadowRoot.innerHTML = `
      <style>
        :host { display: flex; align-items: center; gap: 12px; }
        img { width: 48px; height: 48px; border-radius: 50%; }
      </style>
      <img src="${this.getAttribute('avatar')}" alt="" />
      <span>${this.getAttribute('name')}</span>
    `;
  }
}

customElements.define('user-card', UserCard);

Использование — как обычный HTML:

<user-card name="Светлана" avatar="/img/avatar.jpg"></user-card>

Lifecycle callbacks:

  • connectedCallback — элемент добавлен в DOM (аналог componentDidMount).
  • disconnectedCallback — удалён из DOM (cleanup, отписки).
  • attributeChangedCallback — изменился наблюдаемый атрибут.
  • adoptedCallback — элемент перемещён в другой document.

Shadow DOM: инкапсуляция, slots, ::part

Shadow DOM создаёт изолированное дерево: стили не протекают наружу и не проникают внутрь. Это решает проблему конфликтов CSS в крупных проектах.

Slots — точки вставки контента извне:

class AlertBox extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        .alert { padding: 16px; border-radius: 8px; background: #fef3cd; }
        .title { font-weight: bold; margin-bottom: 8px; }
      </style>
      <div class="alert">
        <div class="title"><slot name="title">Внимание</slot></div>
        <slot>Содержимое по умолчанию</slot>
      </div>
    `;
  }
}
customElements.define('alert-box', AlertBox);
<alert-box>
  <span slot="title">Ошибка</span>
  Не удалось сохранить данные.
</alert-box>

::part — стилизация изнутри снаружи без нарушения инкапсуляции:

/* Внутри компонента: */
<button part="btn">Click</button>

/* Снаружи: */
alert-box::part(btn) { background: red; }

Declarative Shadow DOM

Раньше Shadow DOM требовал JS для инициализации. Declarative Shadow DOM (поддержка во всех браузерах с 2024) позволяет описать shadow tree прямо в HTML:

<user-card>
  <template shadowrootmode="open">
    <style>:host { display: block; }</style>
    <slot></slot>
  </template>
  Контент внутри shadow root
</user-card>

Это критично для SSR: сервер отдаёт готовый Shadow DOM без ожидания JS. Компонент отображается до загрузки скриптов.

Lit vs vanilla: когда нужна обёртка

Vanilla Web Components работают, но boilerplate утомляет: ручной render, строковые шаблоны, отсутствие реактивности. Lit (от Google) добавляет тонкий слой:

import { LitElement, html, css } from 'lit';

class UserCard extends LitElement {
  static styles = css`
    :host { display: flex; align-items: center; gap: 12px; }
    img { width: 48px; border-radius: 50%; }
  `;

  static properties = {
    name: { type: String },
    avatar: { type: String },
  };

  render() {
    return html`
      <img src=${this.avatar} alt="" />
      <span>${this.name}</span>
    `;
  }
}
customElements.define('user-card', UserCard);

Lit добавляет ~5 КБ (gzip) и даёт: реактивные свойства, эффективный DOM-патчинг (lit-html), декораторы, SSR. Для компонентных библиотек и дизайн-систем — оптимальный выбор.

Vanilla подходит для простых компонентов (кнопка, тултип) без сложного стейта. Lit — для всего остального.

Интеграция с React/Vue/Angular

Web Components — стандарт браузера, поэтому работают везде. Но есть нюансы:

  • React — исторически плохая поддержка (события через addEventListener, свойства через ref). React 19 наконец добавил нативную поддержку custom elements: свойства передаются как props, события работают через on-атрибуты.
  • Vue — отличная поддержка из коробки. Vue сам умеет компилироваться в Web Components через defineCustomElement.
  • Angular — Angular Elements оборачивает компоненты в Custom Elements. Работает, но тянет Angular runtime.

Когда Web Components для interop: дизайн-система, которую используют команды на разных фреймворках. Один набор компонентов — работает везде.

Когда не стоит: внутри одного React-приложения Web Components добавляют сложность без выгоды. Используйте обычные React-компоненты.

Вывод

Web Components в 2026 — зрелая технология для конкретных задач: дизайн-системы, виджеты для встраивания, микрофронтенды. Declarative Shadow DOM решил проблему SSR, Lit убрал boilerplate, React 19 починил interop. Но для приложений целиком React/Vue/Svelte по-прежнему удобнее — у них богаче экосистема и лучше DX для сложного стейта.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.