Shadow DOM и инкапсуляция

Atomico — современная библиотека для создания веб-компонентов на JavaScript, обеспечивающая удобный и декларативный подход к построению UI. Одной из ключевых возможностей веб-компонентов является использование Shadow DOM, который обеспечивает инкапсуляцию стилей и структуры компонента.

Принципы Shadow DOM

Shadow DOM создаёт отдельное DOM-дерево внутри компонента, изолированное от основного документа. Элементы в shadow root не наследуют стили родительского документа, а внешние стили не влияют на содержимое компонента, если не используется механизм ::part или ::slotted.

Основные преимущества Shadow DOM:

  • Инкапсуляция структуры: внутренние элементы компонента недоступны напрямую через глобальные селекторы.
  • Изоляция стилей: внутренние стили компонента не пересекаются с внешними.
  • Повторное использование компонентов: компоненты могут быть безопасно встроены в любой проект без риска конфликтов CSS.

Создание Shadow DOM в Atomico

Atomico использует функцию c для создания компонента и автоматически управляет shadow root. При этом DOM-компонент создается в shadow root по умолчанию, что упрощает работу с инкапсуляцией.

Пример базового компонента с shadow DOM:

import { c, html, css } from "atomico";

const buttonStyles = css`
  button {
    background-color: #007acc;
    color: white;
    border: none;
    padding: 10px 20px;
    border-radius: 4px;
    cursor: pointer;
  }
`;

function Button() {
  return html`
    <button>Click me</button>
  `;
}

Button.styles = buttonStyles;

customElements.define("my-button", c(Button));

В этом примере:

  • Компонент Button оборачивается в shadow root автоматически.
  • Стили определяются через Button.styles и применяются только внутри компонента.
  • Внешние стили страницы не влияют на button.

Работа с slots и ::slotted

Shadow DOM поддерживает слоты, позволяя вставлять внешнее содержимое в компонент. В Atomico слоты используются так же, как и в стандартных веб-компонентах:

function Card() {
  return html`
    <div class="card">
      <slot name="header"></slot>
      <div class="content">
        <slot></slot>
      </div>
    </div>
  `;
}

Card.styles = css`
  .card {
    border: 1px solid #ccc;
    padding: 16px;
    border-radius: 8px;
  }

  ::slotted([slot="header"]) {
    font-weight: bold;
    font-size: 18px;
    margin-bottom: 8px;
  }
`;

Применение в HTML:

<my-card>
  <h2 slot="header">Заголовок карточки</h2>
  <p>Основной контент карточки</p>
</my-card>
  • Контент с slot="header" будет стилизован через ::slotted.
  • Основной контент без имени слота попадёт в default-slot и наследует стили компонента только внутри .content.

Динамическая инкапсуляция

Atomico поддерживает реактивные свойства, что позволяет динамически изменять содержимое внутри shadow root, сохраняя инкапсуляцию.

import { c, html, useState, css } from "atomico";

function Toggle() {
  const [on, setOn] = useState(false);

  return html`
    <button oncl ick=${() => setOn(!on)}>
      ${on ? "Включено" : "Выключено"}
    </button>
  `;
}

Toggle.styles = css`
  button {
    padding: 10px;
    border-radius: 5px;
    border: 1px solid #333;
  }
`;

customElements.define("my-toggle", c(Toggle));
  • Состояние управляется через useState, что обеспечивает реактивность компонентов.
  • Все изменения происходят внутри shadow root, не влияя на внешний документ.

Инкапсуляция и композиция

Shadow DOM в Atomico облегчает композицию компонентов, так как каждый компонент полностью изолирован:

function App() {
  return html`
    <my-card>
      <h2 slot="header">Пример карточки</h2>
      <my-toggle></my-toggle>
    </my-card>
  `;
}
  • Компонент my-toggle работает независимо внутри my-card.
  • Стили my-card не влияют на my-toggle и наоборот.

Заключение по инкапсуляции

Использование shadow DOM в Atomico позволяет строить компоненты с чёткой границей ответственности:

  • Изоляция стилей и структуры.
  • Возможность безопасной интеграции в любой проект.
  • Поддержка реактивного состояния и динамического контента.
  • Эффективная работа со слотами для гибкой композиции.

Такой подход обеспечивает надежное, предсказуемое поведение компонентов и делает архитектуру приложения более модульной.