Обработка событий
React позволяет добавлять обработчики событий прямо в JSX. Обработчики событий - это функции, которые вызываются в ответ на определенное событие, например, клик на элемент, наведение курсора, фокус поля формы и т.д.
You will learn
- Различные способы написания обработчика событий
- Как передавать логику обработки событий от родительского компонента
- Как события распространяются и как их остановить
Добавление обработчиков событий
Чтобы добавить обработчик событий, сначала нужно определить функцию, а затем передать ее в качестве пропа в соответствующий JSX-тег. Например, вот кнопка, у которой пока нет обработчика событий:
export default function Button() { return ( <button> У меня нет обработчика событий </button> ); }
Для того, чтобы при нажатии на кнопку появилось сообщение, выполните эти шаги:
- Определите функцию
handleClick
внутри компонентаButton
. - Внутри функции
handleClick
реализуйте логику, которая будет срабатывать при нажатии на кнопку (используйте функциюalert
для отображения сообщения). - Добавьте
onClick={handleClick}
в JSX-тег<button>
, чтобы связать функцию handleClick с событием клика на кнопке.
export default function Button() { function handleClick() { alert('Вы нажали на меня!'); } return ( <button onClick={handleClick}> Нажмите на меня </button> ); }
Вы определили функцию handleClick
и затем передали ее в качестве пропа в <button>
. handleClick
- это обработчик событий. Функции обработчиков событий:
- Обычно определяются внутри ваших компонентов.
- начинаются со слова
handle
, за которым следует имя события.
Обработчики событий принято называть со слова handle
, за которым следует имя события. Вы часто будете встречать onClick={handleClick}
, onMouseEnter={handleMouseEnter}
и т.п.
Ещё обработчик события можно определить внутри JSX:
<button onClick={function handleClick() {
alert('Вы нажали на меня!');
}}>
Или короче с помощью стрелочной функции:
<button onClick={() => {
alert('Вы нажали на меня!');
}}>
Оба этих варианта эквивалентны. Инлайновые обработчики событий удобны для коротких функций.
Чтение пропсов в обработчиках событий
Поскольку обработчики событий объявляются внутри компонента, они имеют доступ к пропсам компонента. Вот кнопка, которая при нажатии показывает предупреждение со значением своего свойства message:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Воспроизводится!"> Воспроизвести фильм </AlertButton> <AlertButton message="Загружается!"> Загрузить изображение </AlertButton> </div> ); }
Это позволяет двум кнопкам показывать разные сообщения. Попробуйте изменить передаваемые им сообщения.
Передача обработчиков событий в качестве пропсов
Часто вы захотите, чтобы родительский компонент указал обработчик событий для дочернего компонента. Например, для кнопок: в зависимости от места использования компонента Button
вам может потребоваться выполнить разные функции — например, воспроизведение фильма или загрузка изображения.
Для этого передайте компоненту проп, который он получит от своего родителя в качестве обработчика событий, например:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Воспроизводится ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Воспроизвести "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Загружается!')}> Загрузить изображение </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Ведьмина служба доставки" /> <UploadButton /> </div> ); }
Здесь компонент Toolbar
рендерит компоненты PlayButton
и UploadButton
:
PlayButton
передаетhandlePlayClick
в качестве пропаonClick
внутренней кнопке.UploadButton
передает() => alert('Загружается!')
в качестве пропаonClick
внутренней кнопке.
Наконец, ваш компонент Button
принимает проп с именем onClick
. Он передает этот проп напрямую встроенной <button>
браузера с помощью onClick={onClick}
. Это указывает React вызвать переданную функцию при клике.
Если вы используете дизайн-систему, то обычно компоненты, такие как кнопки, содержат стили, но не указывают поведение. Вместо этого компоненты, такие как PlayButton
и UploadButton
, передадут обработчики событий вниз по иерархии.
Название пропсов для обработчиков событий
Встроенные компоненты, такие как <button>
и <div>
, поддерживают только имена событий браузера, такие как onClick
. Однако, при создании своих собственных компонентов, вы можете называть их обработчики событий любым именем, которое вам нравится.
Пропсы для обработчиков событий должны начинаться с on
, за которым следует заглавная буква.
Например, проп onClick
компонента Button
мог бы называться onSmash:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Воспроизводится!')}> Воспроизвести фильм </Button> <Button onSmash={() => alert('Загружается!')}> Загрузить изображение </Button> </div> ); }
В этом примере <button onClick={onSmash}>
показывает, что встроенный элемент <button>
(с маленькой буквы) все еще нуждается в пропе с именем onClick
, но имя пропа, которое получает ваш пользовательский компонент Button
, зависит от вас!
Когда ваш компонент поддерживает несколько взаимодействий, вы можете называть пропсы для обработчиков событий в соответствии с конкретными понятиями вашего приложения. Например, компонент Toolbar
получает обработчики событий onPlayMovie
и onUploadImage
:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Воспроизводится!')} onUploadImage={() => alert('Загружается!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Воспроизвести фильм </Button> <Button onClick={onUploadImage}> Загрузить изображение </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Заметьте, что компонент App
не должен знать, что делает Toolbar
с onPlayMovie
или onUploadImage
. Это детали реализации Toolbar
. Здесь Toolbar
передает их в качестве обработчиков onClick
своим кнопкам, но позже он также может вызывать их по клавиатурному сочетанию. Называние пропсов в соответствии со спецификой вашего приложения, например onPlayMovie
, дает вам гибкость в изменении их использовании в будущем.
Всплытие событий
Обработчики событий также будут перехватывать события от любых дочерних компонентов вашего компонента. Говорят, что событие “всплывает” или “распространяется” вверх по дереву: оно начинается там, где произошло событие, а затем поднимается вверх по дереву.
Этот <div>
содержит две кнопки. У <div>
и каждой кнопки есть свои собственные обработчики onClick
. Какие обработчики вы думаете будут вызваны, когда вы нажмете на кнопку?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('Вы нажали на панель инструментов!'); }}> <button onClick={() => alert('Воспроизводится!')}> Воспроизвести фильм </button> <button onClick={() => alert('Загружается!')}> Загрузить изображение </button> </div> ); }
Если вы нажмете на любую кнопку, то сначала будет выполнен ее обработчик onClick
, а затем обработчик onClick
родительского элемента <div>
. Таким образом, появится два сообщения. Если вы нажмете на саму панель инструментов, то будет выполнен только обработчик onClick
родительского элемента <div>
.
Остановка распространения событий
Обработчики событий получают объект события в качестве единственного аргумента. Этот объект обычно называется e
, что означает “event”. С помощью этого объекта вы можете прочитать информацию о событии.
Объект события также позволяет остановить распространение события. Если вы хотите предотвратить достижение события родительским компонентам, вам необходимо вызвать метод e.stopPropagation()
, как это делает компонент Button
:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <Button onClick={() => alert('Воспроизводится!')}> Воспроизвести фильм </Button> <Button onClick={() => alert('Загружается!')}> Загрузить изображение </Button> </div> ); }
При нажатии на кнопку:
- React вызывает обработчик
onClick
, переданный в<button>
. - Этот обработчик, определенный в компоненте
Button
, выполняет следующее:- Вызывает
e.stopPropagation()
, который предотвращает дальнейшее всплытие события. - Вызывает функцию
onClick
, которая является пропом, переданным из компонентаToolbar
.
- Вызывает
- Эта функция, определенная в компоненте Toolbar, выводит всплывающее окно для кнопки.
- Так как всплытие события было остановлено, обработчик
onClick
родительского элемента<div>
не будет вызван.
В результате e.stopPropagation()
нажатие на кнопки теперь вызывает только одно всплывающее окно (из <button>
), а не два (из <button>
и родительской панели инструментов <div>
). Нажатие кнопки не то же самое, что нажатие на панель инструментов, поэтому остановка распространения события имеет смысл для этого интерфейса.
Deep Dive
В редких случаях вам может понадобиться перехватывать все события на дочерних элементах, даже если они остановили распространение. Например, возможно, вы хотите зарегистрировать каждый клик мыши в аналитике, независимо от логики распространения. Для этого можно добавить Capture
в конец имени события:
<div onClickCapture={() => { /* это происходит в первую очередь */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
Каждое событие распространяется в трех фазах:
- Оно спускается вниз, вызывая все обработчики
onClickCapture
. - Оно вызывает обработчик
onClick
для нажатого элемента. - Оно поднимается вверх, вызывая все обработчики
onClick
.
События захвата полезны для кода, такого как роутеры или аналитика, но, вероятно, вы не будете использовать их в приложении.
Передача обработчиков в качестве альтернативы распространению событий
Обратите внимание, как этот обработчик клика выполняет строку кода, а затем вызывает переданный родителем проп onClick
:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
Вы также можете добавить больше кода в этот обработчик перед вызовом обработчика событий onClick
родительского компонента. Этот шаблон обеспечивает альтернативу распространению. Он позволяет дочернему компоненту обрабатывать событие, а также позволяет родительскому компоненту указывать дополнительное поведение. В отличие от распространения, это не происходит автоматически. Но преимущество этого шаблона заключается в том, что вы можете четко следить за всей цепочкой кода, который выполняется в результате какого-либо события.
Если вы полагаетесь на распространение, и трудно отследить, какие обработчики выполняются и почему, попробуйте использовать этот подход.
Предотвращение стандартного поведения
Некоторые события в браузере имеют связанное со стандартным поведением. Например, событие отправки формы <form>
, которое происходит при нажатии на кнопку внутри нее, по умолчанию перезагружает всю страницу:
export default function Signup() { return ( <form onSubmit={() => alert('Отправление!')}> <input /> <button>Отправить</button> </form> ); }
Вы можете вызвать метод e.preventDefault()
из объекта события, чтобы предотвратить это:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Отправление!'); }}> <input /> <button>Отправить</button> </form> ); }
Не путайте e.stopPropagation()
и e.preventDefault()
. Оба они полезны, но не связаны между собой:
- e.stopPropagation() останавливает срабатывание обработчиков событий, привязанных к тегам выше по иерархии DOM.
- e.preventDefault() предотвращает стандартное поведение браузера для некоторых событий.
Могут ли обработчики событий иметь побочные эффекты?
Конечно! Обработчики событий - это лучшее место для побочных эффектов.
В отличие от функций рендеринга, обработчики событий не обязаны быть чистыми функциями, поэтому это отличное место для изменения чего-то - например, изменения значения ввода в ответ на набор текста или изменения списка при нажатии на кнопку. Однако, чтобы изменить какую-то информацию, сначала нужно иметь способ ее хранения. В React для этого используется состояние компонента - его память. Вы узнаете об этом на следующей странице.
Recap
- Вы можете обрабатывать события, передавая функцию в качестве пропа элементу, например,
<button>
. - Обработчики событий должны передаваться, а не вызываться! Например,
onClick={handleClick}
, а неonClick={handleClick()}
. - Вы можете определять функцию обработчика событий отдельно или встроенно.
- Обработчики событий определяются внутри компонента, поэтому они могут получать доступ к свойствам.
- Вы можете объявить обработчик событий в родительском компоненте и передать его в качестве пропа дочернему элементу.
- Вы можете определять свои собственные пропы обработчика событий с именами, специфичными для вашего приложения.
- События всплывают вверх по иерархии элементов. Чтобы предотвратить это, вызовите
e.stopPropagation()
. - События могут иметь нежелательное поведение по умолчанию браузера. Чтобы предотвратить это, вызовите
e.preventDefault()
. - Явный вызов свойства обработчика событий из дочернего обработчика - хорошая альтернатива всплытию событий.
Challenge 1 of 2: Исправление обработчика событий
При нажатии на эту кнопку фон страницы должен переключаться между белым и черным цветами. Однако, ничего не происходит при нажатии на неё. Исправьте эту проблему. (Не беспокойтесь о логике внутри handleClick
- она в порядке.)
export default function LightSwitch() { function handleClick() { let bodyStyle = document.body.style; if (bodyStyle.backgroundColor === 'black') { bodyStyle.backgroundColor = 'white'; } else { bodyStyle.backgroundColor = 'black'; } } return ( <button onClick={handleClick()}> Переключить фон </button> ); }