Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое делегирование событий
Делегирование событий (Event Delegation) - это техника, при которой вместо того, чтобы вешать обработчик события на каждый элемент, вы вешаете один обработчик на родителя и используете механизм всплытия события (event bubbling) для определения целевого элемента.
Как события всплывают (Bubbling)
Когда происходит событие, оно проходит две фазы:
1. Capture фаза (спуск)
window -> document -> html -> body -> div -> button
2. Target фаза
Клик произошел на кнопке
3. Bubbling фаза (подъем)
button -> div -> body -> html -> document -> window
Это очень важно для делегирования:
// Событие click на <button> всплывает до родителя
<div id="parent">
<button id="button">Click me</button>
</div>
const parent = document.getElementById('parent');
parent.addEventListener('click', (event) => {
console.log('Событие поймано на родителе');
console.log(event.target); // <button>
});
Проблема без делегирования
<ul id="list">
<li><a href="#">Item 1</a></li>
<li><a href="#">Item 2</a></li>
<li><a href="#">Item 3</a></li>
<li><a href="#">Item 100</a></li>
</ul>
Без делегирования нужно вешать обработчик на КАЖДУЮ ссылку:
// Плохо: 100 обработчиков!
const links = document.querySelectorAll('a');
links.forEach(link => {
link.addEventListener('click', handleClick);
});
function handleClick(event) {
event.preventDefault();
console.log('Clicked:', event.target.textContent);
}
Проблемы:
- Много кода
- Много памяти (100 обработчиков)
- Если добавить новую ссылку динамически - обработчик не будет на ней
Решение: Делегирование событий
// Хорошо: 1 обработчик на родителе
const list = document.getElementById('list');
list.addEventListener('click', (event) => {
if (event.target.tagName === 'A') {
event.preventDefault();
console.log('Clicked:', event.target.textContent);
}
});
Преимущества:
- Один обработчик вместо 100
- Экономим память
- Динамически добавленные элементы автоматически ловятся
- Код чище и понятнее
Практический пример: Корзина товаров
function ShoppingCart() {
const [items, setItems] = useState([
{ id: 1, name: 'Item 1', quantity: 1 },
{ id: 2, name: 'Item 2', quantity: 2 },
{ id: 3, name: 'Item 3', quantity: 1 },
]);
const handleCartClick = (event: React.MouseEvent) => {
const button = (event.target as HTMLElement).closest('button');
if (!button) return;
const itemId = parseInt(button.dataset.itemId!);
const action = button.dataset.action;
if (action === 'increase') {
setItems(items.map(item =>
item.id === itemId
? { ...item, quantity: item.quantity + 1 }
: item
));
} else if (action === 'decrease') {
setItems(items.map(item =>
item.id === itemId
? { ...item, quantity: Math.max(0, item.quantity - 1) }
: item
));
} else if (action === 'remove') {
setItems(items.filter(item => item.id !== itemId));
}
};
return (
<div onClick={handleCartClick}>
{items.map(item => (
<div key={item.id} className="cart-item">
<span>{item.name}</span>
<span>Qty: {item.quantity}</span>
<button data-item-id={item.id} data-action="decrease">
-
</button>
<button data-item-id={item.id} data-action="increase">
+
</button>
<button data-item-id={item.id} data-action="remove">
Remove
</button>
</div>
))}
</div>
);
}
Вместо:
// Плохо: обработчик на каждой кнопке
{items.map(item => (
<div key={item.id}>
<button onClick={() => handleIncrease(item.id)}>+</button>
<button onClick={() => handleDecrease(item.id)}>-</button>
<button onClick={() => handleRemove(item.id)}>Remove</button>
</div>
))}
event.target vs event.currentTarget
Это критично для делегирования:
const parent = document.getElementById('parent');
parent.addEventListener('click', (event) => {
console.log(event.target); // На каком элементе случилось событие
console.log(event.currentTarget); // На каком элементе обработчик
});
// <div id="parent">
// <button>Click</button>
// </div>
// Если кликнуть на button:
// event.target = <button>
// event.currentTarget = <div id="parent">
closest() - удобный метод
Для сложных структур используй closest():
<ul class="list">
<li class="item">
<span class="text">Item 1</span>
<button class="delete">Delete</button>
</li>
</ul>
const list = document.querySelector('.list');
list.addEventListener('click', (event) => {
const deleteBtn = (event.target as Element).closest('.delete');
if (deleteBtn) {
const item = deleteBtn.closest('.item');
console.log('Удаление:', item);
}
});
Когда НЕ использовать делегирование
События которые не всплывают:
- focus, blur
- load, unload
- scroll, resize
- mouseenter, mouseleave (используй mouseover/mouseout)
// Не сработает делегирование!
const input = document.getElementById('input');
input.addEventListener('focus', () => {
// Это нужно вешать на сам input
});
Очень глубокие вложенности: Если структура HTML очень глубокая, может быть сложнее определить целевой элемент.
В React делегирование встроено
React использует синтетические события с автоматическим делегированием:
// React автоматически использует делегирование
<ul>
{items.map(item => (
<li key={item.id}>
<button onClick={(e) => handleClick(item.id, e)}>
{item.name}
</button>
</li>
))}
</ul>
// React вешает обработчик на корень и использует
// делегирование для всех click событий
Заключение
Делегирование событий - это:
- Эффективно - один обработчик вместо множества
- Экономит память - меньше обработчиков
- Работает с динамическими элементами - новые элементы автоматически ловятся
- Код чище - логика в одном месте
- Встроено в React - не нужно думать об этом
Это основной паттерн в modern веб-разработке.