Отслеживание изменений слотов

В FAST Element слоты (<slot>) используются для внедрения внешнего содержимого в компонент, обеспечивая высокую гибкость и возможность создания переиспользуемых веб-компонентов. Часто требуется не просто отображать содержимое слота, но и реагировать на его динамические изменения. Для этого FAST Element предоставляет механизмы отслеживания изменений слотов, позволяющие управлять состоянием компонента в зависимости от содержимого.


Слоты и их базовое поведение

Слот представляет собой область внутри компонента, куда может быть передан контент извне. Простейший пример:

import { FASTElement, html } from "@microsoft/fast-element";

const template = html<MyElement>`
  <div>
    <slot></slot>
  </div>
`;

export class MyElement extends FASTElement {}

В этом примере любой контент, переданный внутрь <my-element>, будет отображен внутри <div>.

Важно понимать, что слот не является просто контейнером: он управляет распределением элементов внутри Shadow DOM и автоматически обновляет содержимое при изменениях в родительском DOM. Однако по умолчанию нет прямого способа отследить, когда содержимое слота изменилось, кроме использования стандартных событий браузера.


Отслеживание изменений с помощью slotchange

HTML-слоты генерируют событие slotchange каждый раз, когда их содержимое изменяется. FAST Element позволяет использовать это событие для реактивного управления компонентом:

import { FASTElement, html } from "@microsoft/fast-element";

const template = html<MyElement>`
  <div>
    <slot @slotchange=${x => x.handleSlotChange()}></slot>
  </div>
`;

export class MyElement extends FASTElement {
  handleSlotChange() {
    const slot = this.shadowRoot.querySelector('slot');
    const assignedNodes = slot.assignedNodes({ flatten: true });
    console.log('Содержимое слота изменилось:', assignedNodes);
  }
}

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

  • @slotchange позволяет привязать метод обработчика к событию слота.
  • assignedNodes({ flatten: true }) возвращает все элементы, включая вложенные слоты.
  • Метод assignedNodes или assignedElements — основной способ получить текущее содержимое слота.

Наблюдение за несколькими слотами

Компонент может содержать несколько слотов с различными именами. Для каждого слота можно установить отдельный обработчик:

const template = html<MyElement>`
  <div>
    <slot name="header" @slotchange=${x => x.handleHeaderChange()}></slot>
    <slot name="content" @slotchange=${x => x.handleContentChange()}></slot>
  </div>
`;

export class MyElement extends FASTElement {
  handleHeaderChange() {
    const slot = this.shadowRoot.querySelector('slot[name="header"]');
    const nodes = slot.assignedNodes({ flatten: true });
    console.log('Header обновлен:', nodes);
  }

  handleContentChange() {
    const slot = this.shadowRoot.querySelector('slot[name="content"]');
    const nodes = slot.assignedNodes({ flatten: true });
    console.log('Content обновлен:', nodes);
  }
}

Это позволяет реагировать на изменения в конкретных областях компонента и гибко управлять его состоянием в зависимости от типа переданного контента.


Автоматическое реагирование с использованием реактивных свойств

В FAST Element можно интегрировать слоты с реактивными свойствами, чтобы состояние компонента автоматически обновлялось при изменении содержимого слота. Для этого используется метод assignedNodes вместе с обновлением внутренних свойств:

import { FASTElement, html, observable } from "@microsoft/fast-element";

const template = html<MyElement>`
  <div>
    <slot @slotchange=${x => x.updateSlotNodes()}></slot>
    <p>Количество элементов в слоте: ${x => x.slotCount}</p>
  </div>
`;

export class MyElement extends FASTElement {
  @observable slotCount = 0;

  updateSlotNodes() {
    const slot = this.shadowRoot.querySelector('slot');
    this.slotCount = slot.assignedNodes({ flatten: true }).length;
  }
}

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

  • @observable делает свойство реактивным.
  • Изменение свойства slotCount автоматически вызывает обновление DOM, что избавляет от необходимости вручную управлять рендерингом.
  • Этот способ особенно полезен для создания динамических списков или счетчиков элементов в слоте.

Использование MutationObserver для более глубокого контроля

Событие slotchange информирует о добавлении или удалении узлов, но не отслеживает изменения внутри самих элементов слота. Для таких случаев можно использовать MutationObserver:

export class MyElement extends FASTElement {
  observer;

  connectedCallback() {
    super.connectedCallback();
    const slot = this.shadowRoot.querySelector('slot');
    const nodes = slot.assignedNodes({ flatten: true });

    this.observer = new MutationObserver(mutations => {
      console.log('Изменения внутри элементов слота:', mutations);
    });

    nodes.forEach(node => {
      if (node.nodeType === Node.ELEMENT_NODE) {
        this.observer.observe(node, { childList: true, subtree: true, attributes: true });
      }
    });

    slot.addEventListener('slotchange', () => {
      this.observer.disconnect();
      const updatedNodes = slot.assignedNodes({ flatten: true });
      updatedNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
          this.observer.observe(node, { childList: true, subtree: true, attributes: true });
        }
      });
    });
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.observer.disconnect();
  }
}

Этот подход позволяет отслеживать любые изменения внутри элементов, переданных в слот: добавление/удаление дочерних элементов, изменение атрибутов и структуры DOM.


Практические рекомендации

  1. Для большинства случаев достаточно использовать slotchange для реакции на добавление или удаление элементов.
  2. Если требуется контроль над внутренним содержимым элементов слота, нужно применять MutationObserver.
  3. Для динамических интерфейсов желательно сочетать слоты с реактивными свойствами, чтобы обновление происходило автоматически без лишних обработчиков.
  4. В компонентах с множеством слотов следует назначать отдельные обработчики для каждого слота, чтобы минимизировать ненужные перерасчеты состояния.

Отслеживание изменений слотов в FAST Element является мощным инструментом для создания динамических, гибких веб-компонентов, позволяя реагировать на внешние изменения и поддерживать согласованное состояние интерфейса.