Обработка событий в компоненте

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


Подписка на события через атрибуты

События в Haunted чаще всего привязываются к элементам через атрибуты с префиксом on, аналогично JSX в React.

import { html, component } from 'haunted';

function Counter() {
  let count = 0;

  const increment = () => count++;

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

customElements.define('my-counter', component(Counter));
  • Символ @ перед названием события (@click) является синтаксическим сахаром Haunted для addEventListener.
  • Функция-обработчик может напрямую изменять локальные переменные компонента.
  • Для реактивного обновления состояния используется возвращаемое значение из хуков useState или изменение наблюдаемых значений.

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

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

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

function Toggle() {
  const [active, setActive] = useState(false);

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

customElements.define('my-toggle', component(Toggle));

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

  • useState создаёт реактивное состояние, которое автоматически перерисовывает компонент при изменении.
  • Событийная функция напрямую вызывает setActive, обеспечивая синхронное обновление интерфейса.

Декларативная обработка событий с параметрами

Иногда необходимо передать аргументы в обработчик. В Haunted это делается через замыкания:

function List() {
  const items = ['Apple', 'Banana', 'Cherry'];

  const handleClick = (item) => () => console.log(item);

  return html`
    <ul>
      ${items.map(item => html`
        <li @click=${handleClick(item)}>${item}</li>
      `)}
    </ul>
  `;
}

customElements.define('my-list', component(List));
  • Внутренняя функция создаёт замыкание для каждого элемента списка.
  • Это позволяет использовать динамические параметры внутри событий без потери контекста.

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

Haunted позволяет создавать и отправлять кастомные события, которые могут быть пойманы родительскими компонентами или другими элементами DOM.

function Child() {
  const notify = () => {
    const event = new CustomEvent('notify', {
      detail: { message: 'Привет из дочернего компонента' },
      bubbles: true,
      composed: true
    });
    dispatchEvent(event);
  };

  return html`<button @click=${notify}>Отправить событие</button>`;
}

customElements.define('child-component', component(Child));

function Parent() {
  const handleNotification = (e) => console.log(e.detail.message);

  return html`<child-component @notify=${handleNotification}></child-component>`;
}

customElements.define('parent-component', component(Parent));
  • bubbles: true и composed: true позволяют событию подниматься по дереву DOM и пересекать границы Shadow DOM.
  • detail используется для передачи данных вместе с событием.
  • Родительский компонент подписывается на кастомное событие так же, как на стандартное.

Обработка событий форм и элементов ввода

Haunted хорошо интегрируется с формами, позволяя управлять состоянием ввода:

import { useState } from 'haunted';

function FormExample() {
  const [value, setValue] = useState('');

  const handleInput = (e) => setValue(e.target.value);

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Отправлено:', value);
  };

  return html`
    <form @submit=${handleSubmit}>
      <input type="text" .value=${value} @input=${handleInput} />
      <button type="submit">Отправить</button>
    </form>
  `;
}

customElements.define('form-example', component(FormExample));
  • Точка перед атрибутом .value обозначает связывание свойства, а не атрибута.
  • @input обеспечивает реактивное обновление состояния при каждом изменении текста.
  • preventDefault() отменяет стандартное поведение формы.

Советы по производительности

  • Избегать создания новых функций внутри шаблона при каждой перерисовке, если обработчик статичен.
  • Для сложных событий лучше вынести обработчик в отдельную функцию вне html шаблона.
  • Использовать useCallback нет необходимости, так как Haunted перерисовывает только при изменении состояния, а не на каждую мутацию DOM.

Взаимодействие с нативными событиями

Любое нативное событие DOM можно использовать напрямую. Например, mouseenter, keydown, focus:

function HoverComponent() {
  const [hovered, setHovered] = useState(false);

  return html`
    <div
      @mouseenter=${() => setHovered(true)}
      @mouseleave=${() => setHovered(false)}
    >
      ${hovered ? 'Наведение' : 'Не наведено'}
    </div>
  `;
}

customElements.define('hover-component', component(HoverComponent));
  • Поддержка всех стандартных событий DOM встроена без дополнительных библиотек.
  • Можно комбинировать с кастомными событиями для сложной логики взаимодействия.

Обработка событий в Haunted строится на комбинации реактивных состояний, замыканий и стандартной модели событий DOM, что позволяет создавать гибкие, производительные и легко поддерживаемые компоненты.