Synthetic Events в React
Что такое Synthetic Events?
Synthetic Events (Синтетические события) — это обёртка React над нативными событиями браузера. React создаёт единую систему событий, которая работает одинаково во всех браузерах.
function Button() {
function handleClick(event) {
console.log(event); // SyntheticEvent, не нативный Event
}
return <button onClick={handleClick}>Click me</button>;
}
Зачем нужны?
Кроссбраузерность
Разные браузеры имеют разные реализации событий. Synthetic Events унифицируют API:
function Input() {
function handleChange(event) {
// event.target.value работает одинаково везде
console.log(event.target.value);
}
return <input onChange={handleChange} />;
}
Производительность
React использует делегирование событий: все обработчики прикрепляются к корневому элементу, а не к каждому элементу отдельно.
// Вместо 1000 обработчиков на каждой кнопке
// React создаёт один обработчик на корне
function List({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>
<button onClick={() => console.log(item.id)}>
{item.name}
</button>
</li>
))}
</ul>
);
}
API Synthetic Events
Synthetic Event имеет тот же интерфейс, что и нативное событие:
function Form() {
function handleSubmit(event) {
event.preventDefault(); // Работает как в нативном событии
event.stopPropagation();
console.log(event.type); // 'submit'
console.log(event.target); // ссылка на элемент
console.log(event.currentTarget); // ссылка на элемент с обработчиком
}
return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}
Event Pooling (до React 17)
В React 16 и ранее события переиспользовались для производительности:
// React 16 и ранее
function handleClick(event) {
console.log(event.type); // 'click'
setTimeout(() => {
console.log(event.type); // null, событие очищено!
}, 0);
}
Если нужно было сохранить событие, использовали event.persist():
function handleClick(event) {
event.persist(); // Сохраняем событие
setTimeout(() => {
console.log(event.type); // 'click', работает!
}, 0);
}
React 17+:
В React 17 event pooling убрали. События больше не очищаются, event.persist() не нужен.
Доступ к нативному событию
Можно получить нативное событие через nativeEvent:
function handleClick(event) {
console.log(event); // SyntheticEvent
console.log(event.nativeEvent); // Нативный Event браузера
// Полезно для специфичных возможностей браузера
console.log(event.nativeEvent.path);
}
Различия с нативными событиями
Именование
React использует camelCase вместо lowercase:
// HTML
<button onclick="handleClick()">Click</button>
// React
<button onClick={handleClick}>Click</button>
Возврат false
В HTML можно вернуть false для предотвращения действия по умолчанию:
<!-- HTML -->
<a href="#" onclick="console.log('clicked'); return false">
Link
</a>
В React нужно явно вызывать preventDefault():
// React
function Link() {
function handleClick(event) {
event.preventDefault();
console.log('clicked');
}
return <a href="#" onClick={handleClick}>Link</a>;
}
Поддерживаемые события
React поддерживает все стандартные события DOM:
События мыши
onClick
onContextMenu
onDoubleClick
onDrag
onDragEnd
onDragEnter
onDragExit
onDragLeave
onDragOver
onDragStart
onDrop
onMouseDown
onMouseEnter
onMouseLeave
onMouseMove
onMouseOut
onMouseOver
onMouseUp
События клавиатуры
function Input() {
function handleKeyDown(event) {
if (event.key === 'Enter') {
console.log('Enter pressed');
}
}
return <input onKeyDown={handleKeyDown} />;
}
События формы
function Form() {
return (
<form
onSubmit={e => e.preventDefault()}
onChange={e => console.log('changed')}
onFocus={e => console.log('focused')}
onBlur={e => console.log('blurred')}
>
<input type="text" />
</form>
);
}
События фокуса
onFocus
onBlur
События touch
onTouchStart
onTouchMove
onTouchEnd
onTouchCancel
Делегирование событий
React 16 и ранее
События прикреплялись к document:
// React прикреплял обработчики к document
document.addEventListener('click', handleAllClicks);
React 17+
События прикрепляются к корневому React-узлу:
// React прикрепляет к корню приложения
const root = document.getElementById('root');
root.addEventListener('click', handleAllClicks);
Это важно при использовании нескольких версий React на одной странице или при интеграции с другими библиотеками.
Особенности работы с событиями
onChange vs onInput
В React onChange работает как нативный onInput — срабатывает при каждом изменении:
function Input() {
const [value, setValue] = useState('');
return (
<input
value={value}
// Срабатывает при каждом нажатии клавиши
onChange={e => setValue(e.target.value)}
/>
);
}
onScroll
onScroll не всплывает в React (как и в DOM), но React предоставляет его для удобства:
function ScrollableDiv() {
function handleScroll(event) {
console.log(event.target.scrollTop);
}
return (
<div onScroll={handleScroll} style={{ height: 200, overflow: 'auto' }}>
{/* Много контента */}
</div>
);
}
Прямое добавление нативных обработчиков
Иногда нужно добавить нативный обработчик напрямую:
function Component() {
const ref = useRef(null);
useEffect(() => {
const element = ref.current;
function handleNativeClick(event) {
console.log('Native click', event);
}
element.addEventListener('click', handleNativeClick);
return () => {
element.removeEventListener('click', handleNativeClick);
};
}, []);
return <div ref={ref}>Click me</div>;
}
Внимание:
Будьте осторожны с прямыми обработчиками — не забывайте их удалять в cleanup функции.
Захват событий
React поддерживает фазу захвата (capture phase):
function Parent() {
return (
<div
onClickCapture={() => console.log('1. Parent capture')}
onClick={() => console.log('3. Parent bubble')}
>
<button
onClickCapture={() => console.log('2. Button capture')}
onClick={() => console.log('4. Button bubble')}
>
Click
</button>
</div>
);
}
// При клике на кнопку выведет:
// 1. Parent capture
// 2. Button capture
// 4. Button bubble
// 3. Parent bubble
Типизация в TypeScript
import { MouseEvent, ChangeEvent, FormEvent } from 'react';
function Component() {
function handleClick(event: MouseEvent<HTMLButtonElement>) {
console.log(event.currentTarget.name);
}
function handleChange(event: ChangeEvent<HTMLInputElement>) {
console.log(event.target.value);
}
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
}
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</form>
);
}
Частые ошибки
Асинхронное использование события (React 16)
// Неправильно в React 16
function handleClick(event) {
setTimeout(() => {
console.log(event.type); // null!
}, 0);
}
// Правильно
function handleClick(event) {
const eventType = event.type; // Сохраняем значение
setTimeout(() => {
console.log(eventType); // 'click'
}, 0);
}
Передача события в setState
// Неправильно
function handleChange(event) {
setState(prevState => ({
...prevState,
value: event.target.value // event может быть очищен
}));
}
// Правильно
function handleChange(event) {
const newValue = event.target.value;
setState(prevState => ({
...prevState,
value: newValue
}));
}
Вывод
Synthetic Events:
- Обёртка над нативными событиями браузера
- Обеспечивают кроссбраузерность
- Используют делегирование для производительности
- Имеют тот же API, что и нативные события
- В React 17+ не очищаются автоматически
- Используют camelCase для именования
- Можно получить нативное событие через
nativeEvent
На собеседовании:
Важно уметь:
- Объяснить, что такое Synthetic Events и зачем они нужны
- Рассказать о делегировании событий
- Объяснить разницу с нативными событиями
- Рассказать об изменениях в React 17 (event pooling)
- Показать, как получить нативное событие