Тема восьмого урока, это события в React. Мы научим наши компоненты «слышать» клики, ввод текста, отправку форм и множество других действий. Именно события превращают статичный сайт в живое, интерактивное веб-приложение. Представьте кнопку, которая ничего не делает или поле ввода, в которое нельзя написать… Звучит скучно, правда? Давайте оживлять.
Обработка событий: onClick, onChange, onSubmit
В обычном HTML мы добавляли обработчики событий прямо в теги, используя атрибуты вроде onclick, onchange и так далее. В React концепция очень похожа, но есть несколько важных и ключевых отличий.
Во-первых, в React имена событий пишутся в camelCase, а не в нижнем регистре. onclick становится onClick, onsubmit — onSubmit.
Во-вторых и это самое главное, в качестве обработчика мы передаем не строку, а функцию. В HTML мы бы написали onclick="handleClick()", а в React мы пишем onClick={handleClick}. Обратите внимание: мы передаем ссылку на функцию, а не вызываем ее (не ставим скобки). Если мы напишем onClick={handleClick()}, то функция выполнится сразу же, в момент рендера компонента, а не в момент клика.
Давайте рассмотрим три самых главных и часто используемых события, с которыми вы будете сталкиваться буквально в каждом проекте.
onClick (реакция на клик)
Событие onClick это, без преувеличения, хлеб и масло интерактивности. Оно вешается на любые кликабельные элементы: кнопки, ссылки, дивы, картинки и т.д.
Представьте, что у нас есть компонент кнопки-счетчика. Мы уже использовали его в прошлых уроках, но сейчас разберем его событие подробнее.
import React, { useState } from 'react'; function Counter() { // Состояние для хранения текущего значения счетчика const [count, setCount] = useState(0); // Обработчик события для клика const handleIncrement = () => { setCount(count + 1); console.log('Счетчик увеличен!'); }; // Обработчик для сброса const handleReset = () => { setCount(0); }; return ( <div> <p>Вы кликнули {count} раз(а)</p> {/* Передаем функции handleIncrement и handleReset как обработчики */} <button onClick={handleIncrement}>Кликни меня!</button> <button onClick={handleReset}>Сбросить</button> </div> ); } export default Counter;
Что здесь происходит? Мы создали две функции-обработчика, handleIncrement и handleReset. Когда пользователь кликает на первую кнопку, React вызывает функцию handleIncrement, которая, в свою очередь, обновляет состояние count через setCount. Это приводит к повторному рендеру компонента и мы видим на экране новое значение.
Очень важно понимать поток данных: событие (клик) -> вызов обработчика -> обновление состояния -> повторный рендер.
А что, если нам нужно передать в обработчик какой-то параметр? Например, идентификатор элемента. Мы не можем написать onClick={handleDelete(item.id)}, потому что это вызовет функцию сразу. Здесь нам на помощь приходят стрелочные функции.
function ToDoList() { const [tasks, setTasks] = useState(['Выучить React', 'Написать приложение', 'Выпить кофе']); const handleDelete = (taskIndex) => { const newTasks = tasks.filter((_, index) => index !== taskIndex); setTasks(newTasks); }; return ( <ul> {tasks.map((task, index) => ( <li key={index}> {task} {/* Используем стрелочную функцию для передачи параметра */} <button onClick={() => handleDelete(index)}>Удалить</button> </li> ))} </ul> ); }
Здесь () => handleDelete(index) создает новую функцию «на лету», которая будет вызвана только при клике и именно эта функция уже вызовет handleDelete с нужным index.
onChange (отслеживаем ввод данных)
Событие onChange это верный спутник всех элементов форм, которые подразумевают ввод или выбор данных: <input>, <textarea>, <select>. Оно срабатывает при каждом изменении значения в поле.
Самый классический пример, это поле ввода для поиска или форма входа.
import React, { useState } from 'react'; function LoginForm() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); // Обработчик для поля email const handleEmailChange = (event) => { setEmail(event.target.value); }; // Обработчик для поля password const handlePasswordChange = (event) => { setPassword(event.target.value); }; const handleSubmit = (event) => { event.preventDefault(); // Предотвращаем перезагрузку страницы (об этом ниже!) console.log('Email:', email, 'Password:', password); // Здесь обычно отправка данных на сервер }; return ( <form onSubmit={handleSubmit}> <div> <label>Email:</label> {/* value и onChange обязательная пара для контролируемых компонентов */} <input type="email" value={email} onChange={handleEmailChange} /> </div> <div> <label>Пароль:</label> <input type="password" value={password} onChange={handlePasswordChange} /> </div> <button type="submit">Войти</button> </form> ); } export default LoginForm;
Обрати внимание на ключевой момент: мы связали значение поля (value={email}) с нашим состоянием и обработчик onChange, который это состояние обновляет. Такой компонент называется «контролируемым» (controlled component). React становится единственным источником истины для значения этого поля. Это рекомендуемый подход в React, так как он дает нам полный контроль над данными.
Объект event это синтетическое событие React, которое является оберткой над нативным браузерным событием. Оно имеет те же свойства и методы, но работает одинаково во всех браузерах. Самый часто используемый event.target, который указывает на DOM-элемент, вызвавший событие. А event.target.value содержит текущее значение этого элемента.
onSubmit (обрабатываем отправку формы)
Когда пользователь заполнил форму и нажал Enter или кнопку «Отправить», происходит событие onSubmit. Обработка этого события идеальное место для того, чтобы проверить все данные формы (валидация) и отправить их на сервер.
В нашем примере с формой входа мы уже использовали onSubmit. Обрати внимание, что мы повесили его на тег <form>, а не на кнопку. Это более правильный и семантичный подход, так как форма может быть отправлена не только кликом по кнопке, но и нажатием Enter в любом из ее полей.
Отмена стандартного поведения с preventDefault()
А теперь давайте поговорим о том, как «усмирить» стандартное поведение браузера. У многих событий в вебе есть действия по умолчанию. Например:
-
При клике по ссылке (
<a>) браузер переходит по указанному адресу. -
При отправке формы (
<form>) браузер пытается отправить данные на URL из атрибутаactionи перезагружает страницу.
В традиционном JavaScript мы бы использовали event.preventDefault() для отмены этого поведения. В React все точно так же, но event, это наш синтетический.
Вернемся к нашему примеру с формой входа. Если бы мы не вызвали event.preventDefault() в функции handleSubmit, браузер попытался бы отправить данные формы, перезагрузив при этом страницу. А мы ведь строим одностраничное приложение (SPA), где перезагрузки страницы это то, чего мы всячески стараемся избегать! Нам нужно перехватить управление, обработать данные самостоятельно (например, отправив их на сервер с помощью fetch или Axios) и обновить интерфейс без перезагрузки.
Давай рассмотрим еще один яркий пример с ссылкой.
function Navigation() { const handleLinkClick = (event) => { // Отменяем стандартное поведение браузера (переход по ссылке) event.preventDefault(); // Теперь мы можем выполнить свою логику, например, // изменить состояние или осуществить навигацию с помощью React Router (это тема будущих уроков!) console.log('Навигация обработана через React!'); }; return ( <nav> {/* Эта ссылка никуда не приведет физически, пока мы не решим, что делать внутри handleLinkClick */} <a href="https://example.com" onClick={handleLinkClick}> Нажми на меня (ничего не произойдет в стандартном смысле) </a> </nav> ); }
Запомни: почти всегда при обработке события onSubmit формы тебе нужно будет вызывать event.preventDefault(). Для ссылок это не всегда обязательно, но становится критически важным, когда ты используешь клиентскую маршрутизацию (например, с React Router).
Практические задачи для закрепления
Выполни эти три задачи, чтобы окончательно закрепить материал. Обязательно пробуй делать их самостоятельно, прежде чем смотреть на решение.
Задача 1: Светофор
Создай компонент TrafficLight. У него должно быть три состояния: «красный», «желтый», «зеленый». При клике на кнопку «Дальше» цвет должен меняться по классическому циклу: красный -> желтый -> зеленый -> желтый -> красный и так далее. Отображай текущий цвет в виде окрашенного круга и текста.
Решение (набросок):
import React, { useState } from 'react'; function TrafficLight() { const lights = ['red', 'yellow', 'green']; const [currentIndex, setCurrentIndex] = useState(0); const handleNext = () => { // Логика циклического переключения индекса setCurrentIndex((prevIndex) => (prevIndex + 1) % lights.length); // Но по правилам: красный -> зеленый -> желтый -> красный. // Тебе нужно будет реализовать более сложную логику с использованием условий или другого массива последовательностей. }; return ( <div> <div style={{ width: '100px', height: '100px', borderRadius: '50%', backgroundColor: lights[currentIndex] }}></div> <p>Текущий сигнал: {lights[currentIndex]}</p> <button onClick={handleNext}>Дальше</button> </div> ); }
Задание: допиши правильную логику переключения!
Задача 2: Список покупок
Создай компонент ShoppingList. Пользователь должен иметь возможность:
-
Вводить название товара в input.
-
Добавлять его в список по нажатию кнопки «Добавить» или клавиши Enter.
-
Удалять товар из списка по клику на него (или на кнопку «Удалить» рядом с ним).
Решение (ключевые моменты):
function ShoppingList() { const [items, setItems] = useState([]); const [inputValue, setInputValue] = useState(''); const handleAddItem = (event) => { event.preventDefault(); // Если форма, предотвращаем отправку if (inputValue.trim()) { setItems([...items, inputValue]); setInputValue(''); // Очищаем input после добавления } }; const handleDeleteItem = (indexToDelete) => { setItems(items.filter((_, index) => index !== indexToDelete)); }; return ( <div> <form onSubmit={handleAddItem}> <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} placeholder="Введите товар..." /> <button type="submit">Добавить</button> </form> <ul> {items.map((item, index) => ( <li key={index} onClick={() => handleDeleteItem(index)}> {item} </li> ))} </ul> </div> ); }
Задача 3: Простой рисовальщик
Создай компонент SimpleDraw. Это должно быть поле (например, div с границей). При движении зажатой мышью внутри этого поля должны появляться точки (или маленькие div), создавая эффект рисования. Подсказка: тебе понадобятся события onMouseDown (начали зажатие), onMouseMove (двигаем мышью) и onMouseUp (отпустили кнопку).
Решение (концепт):
function SimpleDraw() { const [isDrawing, setIsDrawing] = useState(false); const [dots, setDots] = useState([]); const handleMouseDown = (event) => { setIsDrawing(true); // Добавляем первую точку const newDot = { x: event.clientX, y: event.clientY }; setDots(prevDots => [...prevDots, newDot]); }; const handleMouseMove = (event) => { if (!isDrawing) return; // Рисуем только если зажата кнопка const newDot = { x: event.clientX, y: event.clientY }; setDots(prevDots => [...prevDots, newDot]); }; const handleMouseUp = () => { setIsDrawing(false); }; return ( <div style={{ width: '500px', height: '500px', border: '1px solid black', position: 'relative' }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > {dots.map((dot, index) => ( <div key={index} style={{ position: 'absolute', left: dot.x - 2 + 'px', // Центрируем точку top: dot.y - 2 + 'px', width: '4px', height: '4px', backgroundColor: 'black', borderRadius: '50%' }} ></div> ))} </div> ); }
Теперь твои компоненты обрели голос и слух. Они могут реагировать на действия пользователя, что является фундаментом для создания по-настоящему интерактивных приложений. Не бойся экспериментировать с разными событиями (onFocus, onBlur, onKeyDown). Чем больше практики, тем увереннее ты будешь себя чувствовать.
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


