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

SkateJS предоставляет мощный и гибкий механизм для работы с пользовательскими событиями, что позволяет компонентам взаимодействовать между собой и с внешним окружением. Пользовательские события в SkateJS основаны на стандартном механизме DOM-событий, но интегрированы с реактивной моделью компонентов, что делает их удобными для управления состоянием и передачи данных.


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

Для генерации пользовательского события используется конструктор CustomEvent. В SkateJS это чаще всего делается внутри методов компонента, например, при обработке клика или изменения состояния:

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

Ключевые параметры CustomEvent:

  • detail — объект с данными, которые передаются слушателю события.
  • bubbles — определяет, будет ли событие всплывать через дерево DOM.
  • composed — позволяет событию проходить через Shadow DOM.

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


Прослушивание пользовательских событий

События можно слушать стандартным способом через addEventListener. В SkateJS это часто делается в методе connectedCallback или в хуках жизненного цикла компонента:

connectedCallback() {
  super.connectedCallback();
  this.addEventListener('user-logged-in', this.onUserLoggedIn);
}

onUserLoggedIn(event) {
  console.log('Пользователь вошёл с ID:', event.detail.userId);
}

Важно помнить:

  • Использовать removeEventListener при удалении компонента, чтобы избежать утечек памяти.
  • Обрабатывать события аккуратно при использовании вложенных компонентов, чтобы избежать конфликта имен.

Реактивные события через SkateJS props

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

static get props() {
  return {
    count: Number
  };
}

updated(oldProps, newProps) {
  if (oldProps.count !== newProps.count) {
    this.dispatchEvent(new CustomEvent('count-changed', {
      detail: { count: newProps.count },
      bubbles: true
    }));
  }
}

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


Создание событий с обработчиками через декораторы

SkateJS поддерживает использование декораторов для упрощённого связывания событий:

import { event } from 'skatejs';

class MyComponent extends HTMLElement {
  @event('button-clicked')
  handleClick(event) {
    console.log('Кнопка нажата');
  }
}

Декоратор @event автоматически регистрирует обработчик при подключении компонента, освобождая от необходимости вручную добавлять addEventListener.


Передача данных через события

Передача сложных структур данных возможна через поле detail. При этом важно избегать мутаций объекта после отправки события, чтобы слушатели получали консистентные данные:

const data = { items: [1, 2, 3] };
this.dispatchEvent(new CustomEvent('items-updated', { detail: { data } }));

Для иммутабельности данных рекомендуется использовать Object.freeze или копировать объекты перед передачей:

this.dispatchEvent(new CustomEvent('items-updated', { detail: { data: [...data.items] } }));

Всплытие и делегирование событий

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

this.addEventListener('click', (event) => {
  if (event.target.matches('.delete-button')) {
    this.dispatchEvent(new CustomEvent('item-deleted', {
      detail: { id: event.target.dataset.id },
      bubbles: true
    }));
  }
});

Такой подход уменьшает количество отдельных слушателей и упрощает архитектуру компонентов.


Комбинирование пользовательских событий с Shadow DOM

Когда компонент использует Shadow DOM, необходимо учитывать, что события не всегда проходят наружу. Для этого используется флаг composed: true:

this.dispatchEvent(new CustomEvent('form-submitted', {
  detail: { formData: this.getFormData() },
  bubbles: true,
  composed: true
}));

Без composed: true событие останется внутри Shadow Root и будет недоступно для внешних компонентов.


Обработка ошибок в событиях

При работе с пользовательскими событиями рекомендуется предусматривать обработку исключений в слушателях, чтобы ошибка в одном обработчике не блокировала выполнение других:

this.addEventListener('data-loaded', (event) => {
  try {
    processData(event.detail);
  } catch (err) {
    console.error('Ошибка обработки события data-loaded:', err);
  }
});

Практическая стратегия именования

  • Использовать дефисы в именах событий (user-logged-in, count-changed) для соблюдения стандартов HTML.
  • Для событий, инициируемых компонентом, использовать глаголы в прошедшем времени (clicked, changed, submitted), чтобы отразить факт совершённого действия.
  • Для событий глобального характера — избегать слишком общих названий (update, change), чтобы снизить вероятность конфликта с другими библиотеками.

Резюме механизмов пользовательских событий

  • SkateJS строится на стандарте CustomEvent, обеспечивая полную совместимость с DOM.
  • Связывание событий с props позволяет реализовать реактивное уведомление внешних компонентов.
  • Декораторы упрощают регистрацию и обработку событий.
  • Shadow DOM требует composed: true для передачи событий наружу.
  • Делегирование и иммутабельность данных повышают надежность и масштабируемость компонентов.

Пользовательские события в SkateJS являются универсальным инструментом для построения сложных интерфейсных компонентов и систем взаимодействия между ними. Они сочетают в себе гибкость стандартного DOM и возможности реактивного управления состоянием компонентов.