CustomEvent API

Haunted — это библиотека для создания веб-компонентов на основе функционального подхода и Hooks, вдохновлённого React. Важным элементом взаимодействия компонентов между собой является CustomEvent API, стандартный механизм браузера для передачи событий с данными. Haunted интегрирует его естественным образом, позволяя компонентам создавать и слушать события без лишнего шума.


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

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

const event = new CustomEvent('имя_события', {
  detail: данные,          // передаваемые данные
  bubbles: true,           // всплытие события
  composed: true           // пересечение Shadow DOM
});

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

В Haunted-компонентах элементом для диспатча обычно служит root element, возвращаемый html функцией.

import { html, component } from 'haunted';

function Counter() {
  let count = 0;

  const increment = () => {
    count++;
    const event = new CustomEvent('count-changed', {
      detail: { count },
      bubbles: true,
      composed: true
    });
    this.dispatchEvent(event);
  };

  return html`
    <button @click=${increment}>Увеличить: ${count}</button>
  `;
}

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

В этом примере каждый клик по кнопке создаёт событие count-changed, которое несёт текущий счёт.


Подписка на события

Слушать события можно стандартным способом через addEventListener. Haunted-компоненты позволяют использовать это как на родительском элементе, так и внутри других компонентов.

const parent = document.querySelector('#parent');
parent.addEventListener('count-changed', (e) => {
  console.log('Новое значение:', e.detail.count);
});

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

  • composed: true позволяет событию проходить через Shadow DOM, иначе событие может быть недоступно родителю.
  • Использование хуков, таких как useEffect, позволяет динамически подписываться на события внутри компонента.
import { html, component, useEffect } from 'haunted';

function Logger() {
  useEffect(() => {
    const handler = (e) => console.log('Count обновлён:', e.detail.count);
    window.addEventListener('count-changed', handler);
    return () => window.removeEventListener('count-changed', handler);
  }, []);

  return html`<div>Слушатель запущен</div>`;
}

customElements.define('my-logger', component(Logger));

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

По умолчанию события внутри Shadow DOM не видны внешнему DOM. Чтобы события могли всплывать через Shadow DOM, необходимо устанавливать composed: true. Это ключевой момент при работе с Haunted, поскольку компоненты по умолчанию создаются в Shadow DOM:

const event = new CustomEvent('custom-action', { detail: { foo: 'bar' }, bubbles: true, composed: true });
this.dispatchEvent(event);
  • bubbles: true — позволяет событию подниматься вверх по DOM.
  • composed: true — позволяет событию «выходить» из Shadow DOM, делая его доступным родительским элементам.

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

CustomEvent в Haunted часто применяется для связи нескольких компонентов без глобального состояния:

function ToggleButton() {
  let active = false;

  const toggle = () => {
    active = !active;
    this.dispatchEvent(new CustomEvent('toggle', { detail: { active }, bubbles: true, composed: true }));
  };

  return html`
    <button @click=${toggle}>
      ${active ? 'Включено' : 'Выключено'}
    </button>
  `;
}

function StatusDisplay() {
  useEffect(() => {
    const handler = (e) => {
      this.textContent = `Статус: ${e.detail.active ? 'Активен' : 'Неактивен'}`;
    };
    window.addEventListener('toggle', handler);
    return () => window.removeEventListener('toggle', handler);
  }, []);

  return html`<div>Статус: Неизвестен</div>`;
}

В этом примере ToggleButton посылает событие, а StatusDisplay реагирует на него, обновляя своё отображение. Такой подход исключает необходимость использования глобального состояния, сохраняя функциональный подход Haunted.


Деструктуризация события

Часто для удобства вытаскивают данные прямо из detail:

element.addEventListener('count-changed', ({ detail: { count } }) => {
  console.log('Текущее значение:', count);
});

Это упрощает чтение кода и делает обработчики событий более лаконичными.


Важные рекомендации

  • Всегда устанавливать bubbles и composed в true, если событие должно быть доступно родительским компонентам.
  • Для передачи больших объёмов данных через detail можно использовать объекты с описательными ключами.
  • Использование useEffect для подписки на события предотвращает утечки памяти, автоматически удаляя слушатели при размонтировании компонента.
  • События в Haunted — основной инструмент связи между независимыми компонентами, особенно при отказе от глобального состояния.

CustomEvent API в Haunted обеспечивает гибкую и безопасную передачу данных между компонентами, сохраняя декларативность и функциональный стиль, характерный для библиотеки. Его правильное использование позволяет строить сложные интерфейсы без лишней зависимости от внешних менеджеров состояния.