Всплытие и перехват

SkateJS — это библиотека для создания веб-компонентов на основе стандартов Custom Elements, предоставляющая удобные механизмы управления жизненным циклом компонентов и событий. Одной из ключевых особенностей является гибкая работа с событиями: их всплытие, перехват и управление поведением. В этой статье рассматривается детально механизм событий в SkateJS и методы их контроля.


Основы событийного механизма

В SkateJS компоненты создаются как расширения HTML-элементов, и события в них ведут себя аналогично стандартным DOM-событиям. Каждое событие обладает свойствами:

  • type — тип события (например, 'click', 'input', 'custom-event');
  • bubbles — определяет, будет ли событие всплывать по DOM;
  • cancelable — разрешает отмену стандартного поведения;
  • composed — позволяет событию проходить сквозь Shadow DOM.

Пример создания собственного события внутри компонента:

import { define, Component, h } from 'skatejs';

class MyComponent extends Component {
  static get is() { return 'my-component'; }

  connectedCallback() {
    super.connectedCallback();
    this.dispatchEvent(new CustomEvent('custom-event', {
      bubbles: true,
      cancelable: true,
      composed: true,
      detail: { message: 'Hello SkateJS' }
    }));
  }

  renderCallback() {
    return <div>Пример компонента</div>;
  }
}

define(MyComponent);

В этом примере событие custom-event будет всплывать через Shadow DOM благодаря свойству composed: true и может быть перехвачено родительским элементом.


Всплытие событий

Событие всплывает от элемента, на котором оно было вызвано, к его родителям в DOM, если свойство bubbles установлено в true. В SkateJS это особенно важно для компонентов с Shadow DOM, так как стандартное поведение DOM не всегда пропускает события через границу Shadow. Для управления всплытием необходимо:

  • Устанавливать bubbles: true для события, если оно должно достигать родителя.
  • Использовать composed: true, если компонент использует Shadow DOM и событие должно выйти наружу.

Пример:

this.dispatchEvent(new CustomEvent('user-clicked', {
  bubbles: true,
  composed: true,
  detail: { id: 123 }
}));

Родительский элемент может перехватить это событие стандартным методом:

parentElement.addEventListener('user-clicked', (event) => {
  console.log('Событие перехвачено', event.detail.id);
});

Перехват и остановка событий

SkateJS поддерживает полное управление событиями через стандартные методы DOM:

  • event.stopPropagation() — останавливает дальнейшее всплытие события;
  • event.stopImmediatePropagation() — останавливает всплытие и другие обработчики на текущем элементе;
  • event.preventDefault() — предотвращает действие по умолчанию, если событие cancelable.

Пример перехвата и остановки события:

this.addEventListener('custom-event', (event) => {
  console.log('Событие поймано на компоненте');
  event.stopPropagation(); // дальше событие не будет всплывать
});

При использовании SkateJS важно учитывать, что все обработчики могут быть определены в рендер-методе компонента или через lifecycle-хуки, например, connectedCallback.


Использование декораторов событий

SkateJS предоставляет возможность объявлять обработчики событий прямо в классе с помощью свойства events:

class MyComponent extends Component {
  static get is() { return 'my-component'; }

  static get events() {
    return {
      'click div': 'handleDivClick'
    };
  }

  handleDivClick(event) {
    console.log('Нажатие на div', event.target);
  }

  renderCallback() {
    return <div>Кликни меня</div>;
  }
}

Здесь обработчик handleDivClick автоматически будет подписан на события click на div внутри компонента. Этот подход упрощает управление событиями, особенно в больших компонентах, и позволяет легко контролировать всплытие и перехват.


События в связке с Shadow DOM

При работе с Shadow DOM важно помнить:

  • События не всегда проходят наружу, если composed не установлен в true.
  • Локальные обработчики внутри компонента получают событие раньше, чем внешние.
  • Для глобального перехвата рекомендуется подписка на событие на родительских элементах или на документе.

Пример:

shadowRoot.addEventListener('custom-event', (event) => {
  console.log('Событие внутри Shadow DOM', event.detail);
});

Пользовательские события и detail

SkateJS позволяет легко передавать данные вместе с событием через свойство detail. Это особенно важно для компонентов с динамическими данными.

this.dispatchEvent(new CustomEvent('update', {
  bubbles: true,
  composed: true,
  detail: { value: this.value }
}));

Обработчик родителя может использовать переданные данные:

parent.addEventListener('update', (event) => {
  console.log('Новое значение:', event.detail.value);
});

Итоговое управление всплытием и перехватом

Ключевые моменты при работе с событиями в SkateJS:

  • Всегда определять bubbles и composed в зависимости от нужного поведения.
  • Использовать stopPropagation и stopImmediatePropagation для локального контроля.
  • Передавать данные через detail для пользовательских событий.
  • Использовать events в классе компонента для структурированного объявления обработчиков.

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