Пользовательские события: CustomEvent
CustomEvent— API браузера для создания и диспетчеризации произвольных DOM-событий с пользовательскими данными, позволяющее выстраивать слабосвязанную событийную коммуникацию между компонентами.
Зачем нужно
В сложных приложениях компонентам нужно общаться, не зная друг о друге. CustomEvent + dispatchEvent реализует паттерн Observer на уровне DOM: один компонент генерирует событие, другие подписываются через addEventListener — никакой прямой связи между ними.
Где используется
- Коммуникация между Web Components
- Vanilla JS приложения без фреймворков
- Уведомления об изменении состояния (корзина, авторизация)
- Кросс-компонентные события (form submit → notification)
Создание и диспетчеризация
// Создаём пользовательское событие
const event = new CustomEvent('user:login', {
detail: { // пользовательские данные
userId: 42,
name: 'Иван',
role: 'admin'
},
bubbles: true, // всплывает по DOM
cancelable: true, // можно отменить через preventDefault
composed: false // не пересекает Shadow DOM (для Web Components)
});
// Диспетчеризуем на элемент
document.dispatchEvent(event);
// Или на конкретный элемент
const btn = document.getElementById('login-btn');
btn.dispatchEvent(event);
Подписка на пользовательское событие
// Слушатель
document.addEventListener('user:login', (event) => {
const { userId, name, role } = event.detail;
console.log(`Вошёл пользователь: ${name} (${role}), id=${userId}`);
showWelcomeMessage(name);
});
// Удаление слушателя
const handler = (e) => console.log(e.detail);
document.addEventListener('cart:update', handler);
document.removeEventListener('cart:update', handler);
Паттерн EventBus
Глобальная шина событий для коммуникации между любыми компонентами:
const EventBus = {
_bus: document.createElement('div'),
on(event, callback) {
this._bus.addEventListener(event, (e) => callback(e.detail));
return this; // для цепочки
},
off(event, callback) {
this._bus.removeEventListener(event, callback);
},
emit(event, data = {}) {
this._bus.dispatchEvent(new CustomEvent(event, { detail: data }));
}
};
// Использование
EventBus.on('cart:add', ({ productId, qty }) => {
updateCartUI(productId, qty);
saveToLocalStorage(productId, qty);
});
EventBus.on('cart:add', ({ productId }) => {
analytics.track('add_to_cart', { productId });
});
// Из любого места приложения:
EventBus.emit('cart:add', { productId: 101, qty: 2 });
Отмена события (cancelable)
document.addEventListener('form:submit', (event) => {
if (!validateForm) {
event.preventDefault(); // отменяем если валидация не прошла
}
});
const submitEvent = new CustomEvent('form:submit', {
cancelable: true,
bubbles: true
});
const wasCancelled = !element.dispatchEvent(submitEvent);
if (!wasCancelled) {
// продолжить отправку
sendData;
}
Web Components и composed
class MyComponent extends HTMLElement {
connectedCallback {
this.shadowRoot.querySelector('button').addEventListener('click', () => {
// composed: true — событие пересекает Shadow DOM boundary
this.dispatchEvent(new CustomEvent('my-click', {
detail: { timestamp: Date.now() },
bubbles: true,
composed: true // без этого родитель не получит событие!
}));
});
}
}
Частые ошибки
1. Отсутствие bubbles: true при подписке на document
const event = new CustomEvent('my-event'); // bubbles: false по умолчанию
div.dispatchEvent(event);
document.addEventListener('my-event', handler); // НЕ сработает!
// Исправление:
new CustomEvent('my-event', { bubbles: true });
2. Передача данных не через detail
// Нельзя добавлять свойства напрямую к событию
const bad = new CustomEvent('test');
bad.myData = 'value'; // игнорируется или не доступно
// Правильно:
const good = new CustomEvent('test', { detail: { myData: 'value' } });
3. Утечка памяти — не удалён слушатель
// Если компонент удалён из DOM, но слушатель остался — утечка
function init() {
const handler = (e) => console.log(e.detail);
document.addEventListener('app:update', handler);
// Нужно сохранить ссылку на handler и удалить при cleanup
}
Связанные темы
- События мыши -- click, mouseover, mouseenter
- События формы -- submit, input, change
- DOM дерево
- _MOC DOM
- _MOC JavaScript