← Назад к вопросам
Есть ли в React оптимизация навешивания handler на элементы?
2.2 Middle🔥 231 комментариев
#React
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Event Delegation и оптимизация обработчиков в React
Event Delegation — это техника, при которой обработчики событий вешаются не на каждый элемент в отдельности, а на общий родительский элемент. React использует эту технику для оптимизации.
Как работает React Event System
Традиционный подход (без оптимизации)
// Если сделать так, браузер создаст 1000 обработчиков
function ItemList() {
const items = Array.from({ length: 1000 }, (_, i) => i);
return (
<ul>
{items.map(id => (
<li key={id} onClick={() => handleClick(id)}>
Item {id}
</li>
))}
</ul>
);
}
// Проблемы:
// - 1000 обработчиков в памяти
// - 1000 запусков при каждом render
// - Медленная инициализация
// - Проблемы с производительностью
Оптимизированный подход (Event Delegation)
// React использует это по умолчанию!
function ItemList() {
const items = Array.from({ length: 1000 }, (_, i) => i);
const handleClick = (e: React.MouseEvent<HTMLUListElement>) => {
// e.target содержит элемент, на который кликнули
const target = e.target as HTMLElement;
const itemId = target.dataset.itemId;
console.log('Clicked item:', itemId);
};
return (
<ul onClick={handleClick}>
{items.map(id => (
<li key={id} data-item-id={id}>
Item {id}
</li>
))}
</ul>
);
}
// Оптимизация:
// - Всего 1 обработчик на <ul>
// - События всплывают и обрабатываются на родителе
// - Меньше памяти, быстрее выполняется
React Event System и Synthetic Events
React имеет собственную систему обработки событий, которая находится выше браузерных событий:
function Button() {
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
// e — это НЕ нативное браузерное событие
// Это SyntheticEvent, обёртка React-а
console.log(e.type); // 'click'
console.log(e.target); // кнопка
// Если нужно настоящее браузерное событие:
console.log(e.nativeEvent);
};
return <button onClick={handleClick}>Click me</button>;
}
// Как это работает под капотом:
// 1. React вешает ровно 1 обработчик на корневой элемент
// 2. Все события от элементов bubble-ят к корню
// 3. React перехватывает их на root и проверяет, кому они принадлежат
// 4. React вызывает правильный обработчик
Когда React НЕ использует Event Delegation
// Внимание: некоторые события НЕ делегируются в React
function Form() {
const handleFocus = () => console.log('focused');
const handleScroll = () => console.log('scrolled');
const handlePlay = () => console.log('playing');
return (
<div>
{/* Эти события НЕ делегируются */}
<input onFocus={handleFocus} />
<div onScroll={handleScroll} />
<video onPlay={handlePlay} />
</div>
);
}
// Список не-делегируемых событий:
// - focus, blur
// - mouseenter, mouseleave
// - scroll
// - play, pause, playing и другие медиа-события
// - reset, submit (на forms)
// - abort, load, error
// - toggle
// Для этих событий React вешает обработчик ПРЯМО на элемент
Производительность: Event Delegation
// Бенчмарк: 10,000 элементов
// БЕЗ оптимизации (старый код или неправильное использование)
function BadList() {
return (
<div>
{[...Array(10000)].map((_, i) => (
<button key={i} onClick={() => alert(i)}>
{i}
</button>
))}
</div>
);
}
// Результаты:
// - Инициализация: 2000ms
// - Память: 5MB+
// - Re-render: медленный
// С оптимизацией (Event Delegation)
function OptimizedList() {
const handleClick = (e: React.MouseEvent) => {
const button = e.target as HTMLElement;
alert(button.dataset.id);
};
return (
<div onClick={handleClick}>
{[...Array(10000)].map((_, i) => (
<button key={i} data-id={i}>
{i}
</button>
))}
</div>
);
}
// Результаты:
// - Инициализация: 100ms
// - Память: 50KB
// - Re-render: быстрый
Реальный пример: Таблица с actions
interface TableRow {
id: string;
name: string;
status: string;
}
interface TableProps {
rows: TableRow[];
onEdit: (id: string) => void;
onDelete: (id: string) => void;
}
// Плохая реализация: обработчик на каждый button
function BadTable({ rows, onEdit, onDelete }: TableProps) {
return (
<table>
<tbody>
{rows.map(row => (
<tr key={row.id}>
<td>{row.name}</td>
<td>{row.status}</td>
<td>
{/* 1000 обработчиков для 1000 строк */}
<button onClick={() => onEdit(row.id)}>Edit</button>
<button onClick={() => onDelete(row.id)}>Delete</button>
</td>
</tr>
))}
</tbody>
</table>
);
}
// Хорошая реализация: Event Delegation
function GoodTable({ rows, onEdit, onDelete }: TableProps) {
const handleAction = (e: React.MouseEvent<HTMLTableElement>) => {
const button = e.target as HTMLElement;
const row = button.closest('tr');
if (!row) return;
const id = row.dataset.rowId;
const action = button.dataset.action;
if (action === 'edit') {
onEdit(id);
} else if (action === 'delete') {
onDelete(id);
}
};
return (
<table onClick={handleAction}>
<tbody>
{rows.map(row => (
<tr key={row.id} data-row-id={row.id}>
<td>{row.name}</td>
<td>{row.status}</td>
<td>
{/* Всего 1 обработчик на всю таблицу */}
<button data-action="edit">Edit</button>
<button data-action="delete">Delete</button>
</td>
</tr>
))}
</tbody>
</table>
);
}
Зачем React это делает
// React Event System даёт преимущества:
// 1. Нормализация событий (кроссбраузерность)
function App() {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// В IE этот обработчик будет работать так же, как в Chrome
console.log(e.target.value);
};
return <input onChange={handleChange} />;
}
// 2. Автоматическая очистка при unmount
function Component() {
useEffect(() => {
// Обработчик автоматически удаляется при unmount
return () => {
// cleanup
};
}, []);
return <button onClick={() => {}}>Click</button>;
}
// 3. Производительность через delegation
// 1000 элементов = 1 обработчик на root, не 1000
// 4. Правильная работа с virtualized списками
import { FixedSizeList } from 'react-window';
const Row = ({ index, style }) => (
<div style={style} onClick={() => handleItemClick(index)}>
Item {index}
</div>
);
// Даже если на экране видно 50 элементов из 10000,
// React оптимально управляет обработчиками
Минусы Event Delegation
// 1. Нужно проверять target
function List() {
const handleClick = (e: React.MouseEvent<HTMLUListElement>) => {
const item = (e.target as HTMLElement).closest('li');
if (!item) return; // Клик был не на <li>
console.log(item.dataset.id);
};
return (
<ul onClick={handleClick}>
<li data-id="1">Item 1</li>
</ul>
);
}
// 2. Может быть сложнее отлаживать
// Обработчик на родителе, а событие от child — путанница
// 3. stopPropagation() может сломать delegation
function List() {
const handleClick = (e: React.MouseEvent<HTMLUListElement>) => {
console.log('list clicked');
};
return (
<ul onClick={handleClick}>
<li>
<button
onClick={(e) => {
e.stopPropagation(); // Событие не дойдёт до <ul>
console.log('button clicked');
}}
>
Button
</button>
</li>
</ul>
);
}
Когда игнорировать Event Delegation
// 1. Очень большие деревья (深深deep nesting)
function DeepTree() {
// Если нужно обрабатывать события глубоко вложенных элементов,
// Event Delegation может быть медленнее из-за поиска target
return (
<div onClick={(e) => {
const deepElement = (e.target as HTMLElement).closest('.deep-nested');
// Долгий поиск в глубокой иерархии
}}>
{/* Deep nesting */}
</div>
);
}
// 2. Требуется высокая точность
function DragDrop() {
// Для drag-and-drop обычно вешают обработчики прямо на элементы
// Delegation может быть неудобна
return (
<div
onDragStart={() => {}}
onDragOver={() => {}}
onDrop={() => {}}
/>
);
}
// 3. Пассивные слушатели (для performance)
function Scroll() {
useEffect(() => {
const handleScroll = () => {
// Пассивные слушатели говорят браузеру:
// "Не жди preventDefault в этом обработчике"
};
// Event Delegation не поддерживает passive listeners
// Пришлось использовать нативный способ
window.addEventListener('scroll', handleScroll, { passive: true });
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return <div>Scrollable</div>;
}
React 17+ изменения
// React 17 изменил Event System:
// Раньше (React <17): события вешались на document
document.addEventListener('click', reactEventHandler);
// Теперь (React 17+): события вешаются на root element
rootElement.addEventListener('click', reactEventHandler);
// Это помогает при использовании нескольких React версий
// или при интеграции с non-React кодом
// Но суть остаётся: Event Delegation на более высоком уровне
Чеклист оптимизации
// Для оптимизации обработчиков в React:
// 1. Проверь, есть ли много похожих обработчиков
// Много кнопок с onClick — кандидат для delegation
// 2. Используй data-* атрибуты для идентификации
<button data-action="delete" data-id="123">Delete</button>
// 3. Вешай обработчик на контейнер
const handleClick = (e) => {
const action = (e.target as HTMLElement).dataset.action;
// handle based on action
};
// 4. Используй useCallback для мемоизации
const handleClick = useCallback((e) => {
// handler code
}, []);
// 5. Рассмотри виртуализацию больших списков
import { FixedSizeList } from 'react-window';
Итог
React автоматически оптимизирует обработчики через Event Delegation:
- Все события перехватываются на root элементе
- React определяет, какому компоненту принадлежит событие
- Вызывает нужный обработчик
- Экономит память и повышает производительность
Для большого количества элементов:
- Используй Event Delegation вручную (вешай на контейнер)
- Используй data-* атрибуты для идентификации
- Проверяй e.target с помощью closest()
Это одна из тех вещей, которые React делает за тебя по умолчанию, но понимание этого механизма помогает писать более оптимальный код.