Haunted — это легковесная библиотека для создания веб-компонентов на основе реактивного подхода, ориентированная на минимальный синтаксис и использование хуков. Одним из ключевых аспектов является управление событиями и их композиция. Композиция событий позволяет объединять, модифицировать и контролировать поток событий в компонентах, обеспечивая гибкую реактивность без усложнения архитектуры.
В Haunted события представляют собой стандартные DOM-события, к которым можно привязать обработчики через хуки или напрямую в шаблоне:
import { html, component, useState } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return html`
<button @click=${increment}>${count}</button>
`;
}
customElements.define('my-counter', component(Counter));
Ключевой момент: Haunted не скрывает DOM-события, а предоставляет возможность интегрировать их с реактивным состоянием через хуки.
Haunted предоставляет хуки useEffect и
useEvent (через расширения) для работы с событиями на более
абстрактном уровне. useEffect позволяет подписываться
на события при монтировании компонента и корректно удалять обработчики
при размонтировании:
import { useEffect } from 'haunted';
function Toggle() {
const [on, setOn] = useState(false);
useEffect(() => {
const handleKey = e => {
if (e.key === 'Enter') setOn(!on);
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [on]);
return html`<div>${on ? 'Включено' : 'Выключено'}</div>`;
}
Особенность: Возврат функции из
useEffect обеспечивает автоматическую очистку обработчиков,
что предотвращает утечки памяти и дублирование событий при повторном
рендеринге.
Композиция событий подразумевает объединение нескольких обработчиков в один поток. Например, при сложных компонентах может быть необходимо реагировать на несколько видов событий одновременно:
function MultiEventButton() {
const [state, setState] = useState('idle');
const handleClick = () => setState('clicked');
const handleMouseOver = () => setState('hovered');
const composedHandler = e => {
handleClick(e);
handleMouseOver(e);
};
return html`<button @click=${composedHandler} @mouseover=${composedHandler}>${state}</button>`;
}
Важный момент: Композиция не должна создавать циклические обновления состояния. Нужно контролировать порядок вызовов и их влияние на реактивные переменные.
Для упрощения композиции часто используют вспомогательные функции, которые объединяют несколько обработчиков:
const composeEvents = (...handlers) => e => handlers.forEach(h => h && h(e));
function Example() {
const [count, setCount] = useState(0);
const [hovered, setHovered] = useState(false);
const increment = () => setCount(c => c + 1);
const hover = () => setHovered(true);
return html`
<button @click=${composeEvents(increment, hover)}>Счет: ${count}, Hovered: ${hovered}</button>
`;
}
Преимущество такого подхода: код становится модульным, легко тестируемым и расширяемым. Любой новый обработчик можно добавить без изменения существующих функций.
Haunted полностью поддерживает кастомные события через стандартный
CustomEvent. Это особенно важно для создания компонентов,
которые обмениваются данными с внешними частями приложения:
function Child() {
const notifyParent = () => {
dispatchEvent(new CustomEvent('child-clicked', {
detail: { time: Date.now() },
bubbles: true,
composed: true
}));
};
return html`<button @click=${notifyParent}>Notify parent</button>`;
}
function Parent() {
const [lastClick, setLastClick] = useState(null);
const handleChildClick = e => setLastClick(e.detail.time);
useEffect(() => {
window.addEventListener('child-clicked', handleChildClick);
return () => window.removeEventListener('child-clicked', handleChildClick);
}, []);
return html`<div>
<child-component></child-component>
<p>Last child click: ${lastClick}</p>
</div>`;
}
Особенности кастомных событий:
bubbles: true позволяет событию подниматься вверх по
DOM.composed: true разрешает проход события через Shadow
DOM.При композиции важно:
stopPropagation() только там, где
необходимо, иначе можно блокировать важные события.const safeCompose = (...handlers) => e => {
for (const h of handlers) {
if (h) h(e);
if (e.defaultPrevented) break;
}
};
useEffect.useCallback для
стабильности ссылок на обработчики.Haunted предоставляет мощный, но лаконичный набор инструментов для работы с событиями. Правильная композиция событий позволяет создавать масштабируемые и легко поддерживаемые интерфейсы, где реактивность и управление состоянием остаются прозрачными и предсказуемыми.