Веб-технологии 3 мин чтения

Руководство по разработке масштабируемых веб-компонентов с использованием Shadow DOM, Custom Elements и Slots

PROFSCODE Ekibi 10 июл 2026
Paylaş:
Модульная и Многоразовая UI-Разработка с Веб-Компонентами

В современных веб-приложениях управление и повторное использование UI-компонентов является постоянной проблемой для разработчиков. Хотя фреймворки JavaScript предлагают решения, обмен компонентами между различными фреймворками или полностью независимо часто бывает затруднительным. Именно здесь на помощь приходят веб-компоненты. Этот набор нативных для браузера стандартов позволяет нам создавать по-настоящему модульные, инкапсулированные и независимые от фреймворков UI-компоненты.

Пользовательские Элементы (Custom Elements): Создавайте свои собственные HTML-теги

Пользовательские элементы, составляющие основу веб-компонентов, позволяют определять собственные HTML-теги. Это дает вам возможность создавать специфичные для проекта, осмысленные теги (например, <my-card>) вместо существующих тегов, таких как <button> или <div>. Чтобы создать пользовательский элемент, вы расширяете класс <code>HTMLElement</code> и регистрируете его с помощью метода <code>customElements.define()</code>.


class MyCard extends HTMLElement {
  constructor() {
    super(); // Always call super() first in constructor
    this.innerHTML = `
      <style>
        .card {
          border: 1px solid #ccc;
          padding: 16px;
          border-radius: 8px;
          box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
        }
        h3 {
          color: #333;
        }
      </style>
      <div class="card">
        <h3>Заголовок</h3>
        <p>Это компонент карточки.</p>
      </div>
    `;
  }
}

customElements.define('my-card', MyCard);

После добавления этого кода в ваш HTML-файл вы можете использовать свой компонент на странице с помощью тега <code><my-card></my-card></code>.

Shadow DOM: Инкапсуляция с изоляцией компонентов

Пользовательские элементы — это первый шаг к созданию компонента. Однако для предотвращения конфликтов стилей и поведения нам нужна инкапсуляция. Здесь вступает в игру Shadow DOM. Shadow DOM создает скрытое дерево DOM, которое изолирует внутреннюю структуру и стили вашего компонента от основного документа. Таким образом, правила CSS внутри вашего компонента не просачиваются наружу, а внешние правила CSS не влияют на внутреннее содержимое вашего компонента.


class MyShadowCard extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' }); // Attach a shadow root
    shadowRoot.innerHTML = `
      <style>
        .card {
          border: 1px solid #007bff; /* Different border color */
          padding: 20px;
          border-radius: 10px;
          box-shadow: 3px 3px 10px rgba(0,123,255,0.2);
          font-family: Arial, sans-serif;
          background-color: #e9f5ff;
        }
        h3 {
          color: #0056b3;
          margin-top: 0;
        }
        p {
          color: #444;
          line-height: 1.6;
        }
      </style>
      <div class="card">
        <h3>Карточка Shadow DOM</h3>
        <p>Стили этой карточки полностью инкапсулированы с помощью Shadow DOM.</p>
      </div>
    `;
  }
}

customElements.define('my-shadow-card', MyShadowCard);

Теперь, когда вы используете <code><my-shadow-card></my-shadow-card></code>, вы увидите, что его внутренние правила стилей работают независимо от остальной страницы. <code>mode: 'open'</code> означает, что вы можете получить доступ к Shadow DOM с помощью JavaScript; в режиме <code>'closed'</code> доступ невозможен.

Слоты (Slots): Динамическое распределение контента

Но что, если мы хотим вставить динамический контент в наш компонент извне? Например, если заголовок или содержимое карточки будут отличаться при каждом использовании? Здесь на помощь приходит элемент HTML <code><slot></code>. Слоты позволяют размещать контент, поступающий извне Shadow DOM, в определенных точках теневого дерева вашего компонента.


class MySlottedCard extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `
      <style>
        .card {
          border: 1px dashed #28a745; /* Different border style */
          padding: 15px;
          margin-bottom: 10px;
          border-radius: 5px;
          background-color: #f0fff4;
        }
        ::slotted(h3) { /* Styling content passed through slots */
          color: #218838;
          font-size: 1.5em;
        }
        ::slotted(p) {
          color: #333;
        }
      </style>
      <div class="card">
        <slot name="card-title"><h3>Заголовок по умолчанию</h3></slot>
        <slot><p>Контент по умолчанию.</p></slot>
        <footer><small>Создано с помощью веб-компонента.</small></footer>
      </div>
    `;
  }
}

customElements.define('my-slotted-card', MySlottedCard);

При использовании этого компонента вы можете размещать контент в соответствии с именами слотов:


<my-slotted-card>
  <h3 slot="card-title">Пользовательский заголовок карточки</h3>
  <p>Содержимое этой карточки поступает извне.</p>
  <p>Благодаря слотам у нас гибкая структура.</p>
</my-slotted-card>

<my-slotted-card>
  <h3 slot="card-title">Другой заголовок</h3>
  <p>Может использоваться только с одним абзацем.</p>
</my-slotted-card>

<my-slotted-card>
  <!-- Использовать только контент по умолчанию -->
</my-slotted-card>

Теги <code><slot></code> могут быть названы с использованием атрибута <code>name</code>. Безымянный <code><slot></code> (слот по умолчанию) захватывает весь контент, не назначенный именованным слотам. Вы также можете добавить контент по умолчанию внутри <code><slot></code> чтобы указать, что отображать, если извне ничего не предоставлено.

Продвинутое использование и лучшие практики

  • Коллбэки жизненного цикла (Lifecycle Callbacks): Пользовательские элементы имеют методы жизненного цикла. Например, <code>connectedCallback()</code> запускается при добавлении компонента в DOM, а <code>disconnectedCallback()</code> — при его удалении. Пример:
    
    class MyLifecycleElement extends HTMLElement {
      connectedCallback() {
        console.log('Компонент добавлен в DOM.');
        // Здесь можно добавить слушателей событий.
      }
    
      disconnectedCallback() {
        console.log('Компонент удален из DOM.');
        // Здесь можно очистить слушателей событий.
      }
    }
    customElements.define('my-lifecycle-element', MyLifecycleElement);
    
  • Атрибуты против свойств (Attributes vs. Properties): Атрибуты HTML — это строковые значения, которые отражаются в DOM. Свойства JavaScript могут быть любого типа и хранятся непосредственно в объекте элемента. Для отслеживания изменений атрибутов вы можете использовать <code>static get observedAttributes()</code> и методы <code>attributeChangedCallback(name, oldValue, newValue)</code>.
  • Стилизация веб-компонентов (Styling Web Components): Стили внутри Shadow DOM инкапсулированы и не могут быть напрямую доступны извне. Однако селекторы CSS <code>::part()</code> можно использовать для стилизации определенных внутренних элементов извне. Вы также можете передавать параметры стиля извне компонента, используя <code>пользовательские свойства CSS (переменные CSS)</code>.

Заключение

Веб-компоненты, с их пользовательскими элементами, Shadow DOM и слотами, предлагают мощный стандарт в современной веб-разработке. Благодаря инкапсуляции, повторному использованию и независимости от фреймворков они позволяют создавать более устойчивые, производительные и совместимые веб-приложения. Особенно в больших проектах или в ситуациях, когда несколько проектов должны использовать одни и те же UI-компоненты, веб-компоненты становятся незаменимым инструментом. Изучение этой технологии поднимет вашу фронтенд-архитектуру на новый уровень.

Назад к блогу

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

Пока нет комментариев. Будьте первым!

Отправить комментарий