React не использует нативные DOM-события напрямую. Вместо этого он создаёт SyntheticEvent — кросс-браузерную обёртку над нативным событием. Интерфейс синтетического события идентичен нативному (event.target, event.preventDefault(), event.stopPropagation()), но работает одинаково во всех браузерах.
function Form() {
function handleSubmit(event) {
event.preventDefault() // работает так же как в нативном JS
console.log(event.target) // DOM-элемент формы
console.log(event.nativeEvent) // настоящий браузерный Event
}
return <form onSubmit={handleSubmit}>...</form>
}В React события — это props с именами в camelCase. Значение — функция (не строка как в HTML):
// HTML (старый способ — строка!):
<button onclick="handleClick()">Кнопка</button>
// React (функция):
<button onClick={handleClick}>Кнопка</button>
<input onChange={handleChange} />
<form onSubmit={handleSubmit} />
<div onMouseEnter={handleHover} onMouseLeave={handleLeave} />
<input onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} />Именованная функция (рекомендуется для сложной логики):
function Button({ id, onDelete }) {
function handleClick(event) {
event.stopPropagation()
onDelete(id)
}
return <button onClick={handleClick}>Удалить</button>
}Стрелочная функция inline (удобна для коротких выражений):
<button onClick={() => setCount(c => c + 1)}>+</button>
<input onChange={(e) => setName(e.target.value)} />Передача аргументов — через стрелочную функцию-обёртку:
// Передаём id в обработчик
{items.map(item => (
<li key={item.id}>
{item.text}
<button onClick={() => handleDelete(item.id)}>×</button>
</li>
))}Нельзя писать onClick={handleDelete(item.id)} — это вызов функции, а не передача! Нужна обёртка () => handleDelete(item.id).
// preventDefault — отменяет стандартное поведение браузера
function LoginForm() {
function handleSubmit(e) {
e.preventDefault() // без этого страница перезагрузится!
// наша логика...
}
return <form onSubmit={handleSubmit}>...</form>
}
// stopPropagation — останавливает всплытие события
function Modal({ onClose, children }) {
return (
<div className="overlay" onClick={onClose}>
<div className="modal" onClick={e => e.stopPropagation()}>
{/* клик внутри модала не закроет его */}
{children}
</div>
</div>
)
}Браузерный addEventListener добавляет слушатель на каждый элемент. React использует делегирование событий: один обработчик на корневой элемент (document или #root), который перехватывает все события через всплытие.
// Vanilla JS — обработчик на каждую кнопку:
document.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', handler) // N обработчиков
})
// React — один обработчик на корне:
document.getElementById('root').addEventListener('click', reactEventHandler)
// React сам определяет какой компонент вызватьЭто эффективнее: список из 1000 элементов не создаёт 1000 слушателей.
| JSX prop | Нативное событие | Применение |
|---|---|---|
| onClick | click | кнопки, ссылки |
| onChange | change (input, select) | поля ввода |
| onSubmit | submit | формы |
| onKeyDown | keydown | горячие клавиши |
| onKeyUp | keyup | обработка ввода |
| onFocus / onBlur | focus / blur | поля форм |
| onMouseEnter | mouseenter | всплывающие подсказки |
| onScroll | scroll | бесконечная прокрутка |
// React.MouseEvent<HTMLButtonElement> — тип события клика на кнопку
function Button({ onClick }: { onClick: React.MouseEvent<HTMLButtonElement> }) { ... }
// Правильный способ типизировать обработчики:
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { ... }
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { ... }
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { ... }Делегирование событий и паттерны обработчиков: от addEventListener к React-стилю
// ============================================================
// Часть 1: Делегирование событий — как React экономит память
// ============================================================
// VANILLA JS: отдельный listener на каждый элемент (дорого!)
function addListenersNaive(items) {
let listenerCount = 0
items.forEach(item => {
const el = document.createElement('button')
el.textContent = item.text
el.addEventListener('click', () => {
console.log(`Нажато: ${item.text}`)
})
listenerCount++
})
console.log(`Naive: создано ${listenerCount} слушателей для ${items.length} элементов`)
}
// ДЕЛЕГИРОВАНИЕ (как React): один listener на родителе
function addListenersDelegated(container, items) {
// Один обработчик на весь список
container.addEventListener('click', (event) => {
const button = event.target.closest('[data-id]')
if (!button) return
const id = button.dataset.id
const item = items.find(i => String(i.id) === id)
if (item) console.log(`[Делегирование] Нажато: ${item.text}`)
})
items.forEach(item => {
const el = document.createElement('button')
el.dataset.id = item.id
el.textContent = item.text
container.appendChild(el)
})
console.log(`Delegation: создан 1 слушатель для ${items.length} элементов`)
}
// Демонстрация разницы
const testItems = Array.from({ length: 10 }, (_, i) => ({ id: i + 1, text: `Элемент ${i + 1}` }))
console.log('=== Сравнение подходов ===')
addListenersNaive(testItems) // 10 слушателей
const container = document.createElement('div')
addListenersDelegated(container, testItems) // 1 слушатель
// ============================================================
// Часть 2: Паттерны передачи аргументов в обработчики
// ============================================================
// Имитируем React-компонент с обработчиком событий
function createTodoItem(todo, onDelete, onToggle) {
// React JSX эквивалент:
// return (
// <li>
// <span onClick={() => onToggle(todo.id)}>{todo.text}</span>
// <button onClick={(e) => { e.stopPropagation(); onDelete(todo.id) }}>×</button>
// </li>
// )
// ВАЖНО: onClick={() => onDelete(todo.id)}
// НЕ: onClick={onDelete(todo.id)} — это вызов, а не передача!
return {
text: todo.text,
onSpanClick: () => onToggle(todo.id), // передача аргумента через стрелку
onButtonClick: (e) => {
// e.stopPropagation() — предотвращаем всплытие к span
console.log('[stopPropagation] клик по кнопке не достигнет span')
onDelete(todo.id)
}
}
}
// Имитируем form onSubmit с preventDefault
function createLoginForm(onSubmit) {
return {
handleSubmit(event) {
// В реальном React: event.preventDefault()
// У нас нет реального form, симулируем:
if (event && event.preventDefault) event.preventDefault()
const formData = event?.formData || { email: 'test@test.com', password: '123456' }
console.log('[Form] Отправка предотвращена, данные:', formData)
onSubmit(formData)
}
}
}
// Демонстрация
const todo = { id: 1, text: 'Изучить обработчики событий React' }
const item = createTodoItem(
todo,
(id) => console.log(`Удалён todo #${id}`),
(id) => console.log(`Переключён todo #${id}`)
)
console.log('\n=== Обработчики событий ===')
item.onSpanClick() // Переключён todo #1
item.onButtonClick() // [stopPropagation] + Удалён todo #1
const form = createLoginForm((data) => console.log('Логин:', data.email))
form.handleSubmit({ preventDefault: () => {}, formData: { email: 'user@react.dev' } })React не использует нативные DOM-события напрямую. Вместо этого он создаёт SyntheticEvent — кросс-браузерную обёртку над нативным событием. Интерфейс синтетического события идентичен нативному (event.target, event.preventDefault(), event.stopPropagation()), но работает одинаково во всех браузерах.
function Form() {
function handleSubmit(event) {
event.preventDefault() // работает так же как в нативном JS
console.log(event.target) // DOM-элемент формы
console.log(event.nativeEvent) // настоящий браузерный Event
}
return <form onSubmit={handleSubmit}>...</form>
}В React события — это props с именами в camelCase. Значение — функция (не строка как в HTML):
// HTML (старый способ — строка!):
<button onclick="handleClick()">Кнопка</button>
// React (функция):
<button onClick={handleClick}>Кнопка</button>
<input onChange={handleChange} />
<form onSubmit={handleSubmit} />
<div onMouseEnter={handleHover} onMouseLeave={handleLeave} />
<input onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} />Именованная функция (рекомендуется для сложной логики):
function Button({ id, onDelete }) {
function handleClick(event) {
event.stopPropagation()
onDelete(id)
}
return <button onClick={handleClick}>Удалить</button>
}Стрелочная функция inline (удобна для коротких выражений):
<button onClick={() => setCount(c => c + 1)}>+</button>
<input onChange={(e) => setName(e.target.value)} />Передача аргументов — через стрелочную функцию-обёртку:
// Передаём id в обработчик
{items.map(item => (
<li key={item.id}>
{item.text}
<button onClick={() => handleDelete(item.id)}>×</button>
</li>
))}Нельзя писать onClick={handleDelete(item.id)} — это вызов функции, а не передача! Нужна обёртка () => handleDelete(item.id).
// preventDefault — отменяет стандартное поведение браузера
function LoginForm() {
function handleSubmit(e) {
e.preventDefault() // без этого страница перезагрузится!
// наша логика...
}
return <form onSubmit={handleSubmit}>...</form>
}
// stopPropagation — останавливает всплытие события
function Modal({ onClose, children }) {
return (
<div className="overlay" onClick={onClose}>
<div className="modal" onClick={e => e.stopPropagation()}>
{/* клик внутри модала не закроет его */}
{children}
</div>
</div>
)
}Браузерный addEventListener добавляет слушатель на каждый элемент. React использует делегирование событий: один обработчик на корневой элемент (document или #root), который перехватывает все события через всплытие.
// Vanilla JS — обработчик на каждую кнопку:
document.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', handler) // N обработчиков
})
// React — один обработчик на корне:
document.getElementById('root').addEventListener('click', reactEventHandler)
// React сам определяет какой компонент вызватьЭто эффективнее: список из 1000 элементов не создаёт 1000 слушателей.
| JSX prop | Нативное событие | Применение |
|---|---|---|
| onClick | click | кнопки, ссылки |
| onChange | change (input, select) | поля ввода |
| onSubmit | submit | формы |
| onKeyDown | keydown | горячие клавиши |
| onKeyUp | keyup | обработка ввода |
| onFocus / onBlur | focus / blur | поля форм |
| onMouseEnter | mouseenter | всплывающие подсказки |
| onScroll | scroll | бесконечная прокрутка |
// React.MouseEvent<HTMLButtonElement> — тип события клика на кнопку
function Button({ onClick }: { onClick: React.MouseEvent<HTMLButtonElement> }) { ... }
// Правильный способ типизировать обработчики:
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { ... }
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { ... }
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { ... }Делегирование событий и паттерны обработчиков: от addEventListener к React-стилю
// ============================================================
// Часть 1: Делегирование событий — как React экономит память
// ============================================================
// VANILLA JS: отдельный listener на каждый элемент (дорого!)
function addListenersNaive(items) {
let listenerCount = 0
items.forEach(item => {
const el = document.createElement('button')
el.textContent = item.text
el.addEventListener('click', () => {
console.log(`Нажато: ${item.text}`)
})
listenerCount++
})
console.log(`Naive: создано ${listenerCount} слушателей для ${items.length} элементов`)
}
// ДЕЛЕГИРОВАНИЕ (как React): один listener на родителе
function addListenersDelegated(container, items) {
// Один обработчик на весь список
container.addEventListener('click', (event) => {
const button = event.target.closest('[data-id]')
if (!button) return
const id = button.dataset.id
const item = items.find(i => String(i.id) === id)
if (item) console.log(`[Делегирование] Нажато: ${item.text}`)
})
items.forEach(item => {
const el = document.createElement('button')
el.dataset.id = item.id
el.textContent = item.text
container.appendChild(el)
})
console.log(`Delegation: создан 1 слушатель для ${items.length} элементов`)
}
// Демонстрация разницы
const testItems = Array.from({ length: 10 }, (_, i) => ({ id: i + 1, text: `Элемент ${i + 1}` }))
console.log('=== Сравнение подходов ===')
addListenersNaive(testItems) // 10 слушателей
const container = document.createElement('div')
addListenersDelegated(container, testItems) // 1 слушатель
// ============================================================
// Часть 2: Паттерны передачи аргументов в обработчики
// ============================================================
// Имитируем React-компонент с обработчиком событий
function createTodoItem(todo, onDelete, onToggle) {
// React JSX эквивалент:
// return (
// <li>
// <span onClick={() => onToggle(todo.id)}>{todo.text}</span>
// <button onClick={(e) => { e.stopPropagation(); onDelete(todo.id) }}>×</button>
// </li>
// )
// ВАЖНО: onClick={() => onDelete(todo.id)}
// НЕ: onClick={onDelete(todo.id)} — это вызов, а не передача!
return {
text: todo.text,
onSpanClick: () => onToggle(todo.id), // передача аргумента через стрелку
onButtonClick: (e) => {
// e.stopPropagation() — предотвращаем всплытие к span
console.log('[stopPropagation] клик по кнопке не достигнет span')
onDelete(todo.id)
}
}
}
// Имитируем form onSubmit с preventDefault
function createLoginForm(onSubmit) {
return {
handleSubmit(event) {
// В реальном React: event.preventDefault()
// У нас нет реального form, симулируем:
if (event && event.preventDefault) event.preventDefault()
const formData = event?.formData || { email: 'test@test.com', password: '123456' }
console.log('[Form] Отправка предотвращена, данные:', formData)
onSubmit(formData)
}
}
}
// Демонстрация
const todo = { id: 1, text: 'Изучить обработчики событий React' }
const item = createTodoItem(
todo,
(id) => console.log(`Удалён todo #${id}`),
(id) => console.log(`Переключён todo #${id}`)
)
console.log('\n=== Обработчики событий ===')
item.onSpanClick() // Переключён todo #1
item.onButtonClick() // [stopPropagation] + Удалён todo #1
const form = createLoginForm((data) => console.log('Логин:', data.email))
form.handleSubmit({ preventDefault: () => {}, formData: { email: 'user@react.dev' } })Создай компонент App с кнопкой и полем ввода. При нажатии кнопки значение из поля добавляется в список сообщений внизу. При нажатии Enter в поле — тоже добавляется. После добавления поле очищается. Используй useState для хранения текста и массива сообщений.
useState([]) для массива. В handleAdd проверяй text.trim(). onChange читает e.target.value. onKeyDown проверяет e.key === "Enter". onClick на кнопку — просто {handleAdd} без скобок. setMessages использует функциональное обновление с prev.