Декларативный Shadow DOM

Haunted — это современная библиотека для создания веб-компонентов на базе функциональных подходов, аналогичных React Hooks, но ориентированных на нативный Custom Elements API. Одной из ключевых возможностей является интеграция с Shadow DOM, обеспечивающая изоляцию стилей и структуры компонента. С появлением декларативного Shadow DOM разработка компонентов становится более естественной и удобной.


Основы Shadow DOM

Shadow DOM позволяет создавать изолированные поддеревья DOM, скрытые от глобального контекста страницы. Основные преимущества:

  • Инкапсуляция стилей — стили компонента не затрагивают внешние элементы и наоборот.
  • Изоляция структуры — внутренние элементы компонента недоступны через обычный CSS-селектор из внешнего DOM.
  • Управление жизненным циклом — можно полностью контролировать отрисовку и обновление внутренних элементов компонента.

В традиционном подходе Shadow DOM создавался программно через this.attachShadow({ mode: 'open' }) внутри конструктора кастомного элемента. В Haunted этот процесс можно описывать декларативно, что упрощает разработку и повышает читаемость кода.


Декларативный подход в Haunted

В Haunted для декларативного Shadow DOM используется специальный синтаксис при создании функциональных компонентов. Основные функции:

import { component, html, useState } from 'haunted';

Функция html поддерживает возможность декларативного оборачивания шаблона в Shadow DOM. Это достигается использованием атрибута shadowRootMode:

const MyComponent = () => html`
  <div shadowRootMode="open">
    <p>Привет, Shadow DOM!</p>
  </div>
`;

Ключевые моменты:

  • Атрибут shadowRootMode может принимать значения "open" или "closed".
  • Любые дочерние элементы в шаблоне автоматически помещаются в Shadow Root.
  • Стили, объявленные внутри компонента, применяются только к его внутреннему дереву.

Управление состоянием внутри Shadow DOM

Haunted использует хуки для управления состоянием, и декларативный Shadow DOM полностью совместим с ними:

const Counter = () => {
  const [count, setCount] = useState(0);

  return html`
    <div shadowRootMode="open">
      <p>Счётчик: ${count}</p>
      <button @click=${() => setCount(count + 1)}>Увеличить</button>
    </div>
  `;
};

customElements.define('my-counter', component(Counter));

Особенности такого подхода:

  • Состояние (count) привязано к компоненту и его Shadow DOM.
  • Обновления происходят только внутри изолированного дерева, не влияя на остальную страницу.
  • События можно обрабатывать через стандартные синтаксические конструкции @event.

Стилизация компонентов

Стили можно объявлять внутри Shadow DOM декларативно:

const StyledComponent = () => html`
  <div shadowRootMode="open">
    <style>
      p {
        color: teal;
        font-weight: bold;
      }
    </style>
    <p>Текст с изолированными стилями</p>
  </div>
`;

Преимущества:

  • Локальные стили не конфликтуют с глобальными CSS.
  • Возможность использовать CSS-переменные и инкапсулированные анимации.
  • Совместимость с CSS custom properties позволяет создавать настраиваемые темы внутри Shadow DOM.

Вложенные компоненты и Shadow DOM

Декларативный Shadow DOM поддерживает вложенные компоненты:

const InnerComponent = () => html`
  <div shadowRootMode="open">
    <span>Внутренний компонент</span>
  </div>
`;

const OuterComponent = () => html`
  <div shadowRootMode="open">
    <p>Внешний компонент</p>
    <${InnerComponent} />
  </div>
`;

Особенности:

  • Каждый компонент создаёт собственный Shadow Root.
  • Вложенные компоненты полностью независимы друг от друга.
  • Это обеспечивает модульность и повторное использование компонентов без конфликта стилей.

Динамическое обновление Shadow DOM

Haunted автоматически обновляет Shadow DOM при изменении состояния или свойств компонента:

const ToggleMessage = () => {
  const [visible, setVisible] = useState(true);

  return html`
    <div shadowRootMode="open">
      ${visible ? html`<p>Сообщение видно</p>` : html`<p>Сообщение скрыто</p>`}
      <button @click=${() => setVisible(!visible)}>Переключить</button>
    </div>
  `;
};

Особенности динамики:

  • Изменения состояния мгновенно отражаются в Shadow DOM.
  • Нет необходимости вручную управлять DOM-узлами или перерисовывать компонент.
  • Поддерживаются условные и циклические шаблоны внутри Shadow Root.

Интеграция с глобальными событиями и API

Несмотря на изоляцию, Shadow DOM позволяет взаимодействовать с внешними событиями и API:

const GlobalListener = () => {
  const [data, setData] = useState(null);

  window.addEventListener('resize', () => {
    setData(window.innerWidth);
  });

  return html`
    <div shadowRootMode="open">
      <p>Ширина окна: ${data}</p>
    </div>
  `;
};

Особенности:

  • Shadow DOM изолирует DOM-структуру, но не блокирует глобальные события.
  • Можно использовать сторонние библиотеки и DOM API внутри компонентов.
  • Это позволяет создавать полностью функциональные веб-приложения с декларативным Shadow DOM.

Резюме возможностей

  • Простое объявление Shadow DOM через shadowRootMode.
  • Полная совместимость с хуками Haunted (useState, useEffect).
  • Изоляция стилей и структуры компонентов.
  • Поддержка вложенных компонентов с независимыми Shadow Root.
  • Динамическое обновление при изменении состояния и свойств.
  • Возможность взаимодействия с внешними событиями и глобальными API.

Декларативный Shadow DOM в Haunted сочетает функциональный подход к компонентам с мощной изоляцией DOM и стилей, обеспечивая современный, модульный и удобный способ разработки веб-компонентов.