Реагирование на события
React позволяет добавлять обработчики событий в ваш JSX-код. Обработчики событий — это ваши собственные функции, которые будут запускаться в ответ на такие взаимодействия, как клик, наведение курсора, фокусировка на полях ввода формы и так далее.
Вы узнаете
- Различные способы написания обработчика событий
- Как передать логику обработки событий из родительского компонента
- Как распространяются события и как их остановить
Добавление обработчиков событий
Чтобы добавить обработчик событий, сначала нужно определить функцию, а затем передать ее в качестве проп соответствующему тегу JSX. Например, вот кнопка, которая пока ничего не делает:
export default function Button() {
return (
<button>
Я ничего не делаю
</button>
);
}Вы можете заставить ее отображать сообщение при нажатии пользователем, выполнив следующие три шага:
- Объявите функцию с именем
handleClickвнутри вашего компонентаButton. - Реализуйте логику внутри этой функции (используйте
alertдля отображения сообщения). - Добавьте
onClick={handleClick}к JSX<button>.
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('Вы нажали на меня!');
}}>Все эти стили эквивалентны. Встроенные обработчики событий удобны для коротких функций.
Функции, передаваемые в обработчики событий, должны передаваться, а не вызываться. Например:
| passing a function (correct) | calling a function (incorrect) |
|---|---|
<button onClick={handleClick}> | <button onClick={handleClick()}> |
Разница незначительна. В первом примере функция handleClick передается в качестве обработчика события onClick. Это указывает React запомнить её и вызвать вашу функцию только тогда, когда пользователь нажмёт кнопку.
Во втором примере () в конце handleClick() запускает функцию немедленно во время рендеринга, без каких-либо кликов. Это происходит потому, что JavaScript внутри JSX { и } выполняется сразу.
Когда вы пишете код в строке, та же самая ловушка проявляется по-другому:
| passing a function (correct) | calling a function (incorrect) |
|---|---|
<button onClick={() => alert('...')}> | <button onClick={alert('...')}> |
Передача встроенного кода таким образом не сработает при нажатии — она срабатывает каждый раз, когда компонент рендерится:
// Это предупреждение срабатывает при рендеринге компонента, а не при нажатии!
<button onClick={alert('Вы нажали на меня!')}>Если вы хотите определить обработчик события в строке, оберните его в анонимную функцию, как показано ниже:
<button onClick={() => alert('Вы нажали на меня!')}>Вместо того, чтобы выполнять код внутри при каждом рендеринге, это создает функцию, которая будет вызвана позже.
В обоих случаях вам нужно передать функцию:
<button onClick={handleClick}>передает функциюhandleClick.<button onClick={() => alert('...')}>передает функцию() => alert('...').
Чтение пропсов в обработчиках событий
Поскольку обработчики событий объявляются внутри компонента, они имеют доступ к пропсам компонента. Вот кнопка, при нажатии которой отображается всплывающее окно с пропсом message:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> );}export default function Toolbar() { return ( <div> <AlertButton message="Playing!"> Play Movie </AlertButton> <AlertButton message="Uploading!"> Upload Image </AlertButton> </div> );}Это позволяет этим двум кнопкам отображать разные сообщения. Попробуйте изменить передаваемые им сообщения.
Передача обработчиков событий в качестве props
Часто вам захочется, чтобы родительский компонент задавал обработчик события дочернего компонента. Рассмотрим кнопки: в зависимости от того, где вы используете компонент Button, вам может понадобиться выполнить разные функции — например, одна воспроизводит видео, а другая загружает изображение.
Для этого передайте в качестве обработчика события проп, который компонент получает от своего родителя, следующим образом:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> );}function PlayButton({ movieName }) { function handlePlayClick() { alert(\`Playing \${movieName}!\`); } return ( <Button onClick={handlePlayClick}> Play "{movieName}" </Button> );}function UploadButton() { return ( <Button onClick={() => alert('Uploading!')}> Upload Image </Button> );}export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> );}Здесь компонент Toolbar отображает PlayButton и UploadButton:
PlayButtonпередаетhandlePlayClickв качестве пропonClickвнутреннемуButton.UploadButtonпередает() => alert('Uploading!')в качестве пропonClickвнутреннемуButton.
Наконец, ваш компонент 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('Playing!')}> Play Movie </Button> <Button onSmash={() => alert('Uploading!')}> Upload Image </Button> </div> );}В этом примере <button onClick={onSmash}> показывает, что браузерному <button> (строчными буквами) по-прежнему нужен проп под названием onClick, но как назвать проп, который получает ваш пользовательский компонент Button, решать вам!
Если ваш компонент поддерживает несколько взаимодействий, вы можете называть пропсы обработчиков событий в соответствии с концепциями конкретного приложения. Например, этот компонент Toolbar получает обработчики событий onPlayMovie и onUploadImage:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Playing!')} onUploadImage={() => alert('Uploading!')} /> );}function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Play Movie </Button> <Button onClick={onUploadImage}> Upload Image </Button> </div> );}function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> );}Обратите внимание, что компоненту App не нужно знать, что Toolbar будет делать с onPlayMovie или onUploadImage. Это деталь реализации Toolbar. Здесь Toolbar передает их в качестве обработчиков onClick своим Button-ам, но позже он также может запускать их с помощью сочетания клавиш. Названия пропсов, основанные на взаимодействиях, специфичных для приложения, таких как onPlayMovie, дают вам гибкость для изменения способа их использования в будущем.
Убедитесь, что вы используете подходящие HTML-теги для своих обработчиков событий. Например, для обработки кликов используйте <button onClick={handleClick}> вместо <div onClick={handleClick}>. Использование реального браузерного тега <button> включает встроенные возможности браузера, такие как навигация с помощью клавиатуры. Если вам не нравится стиль кнопки по умолчанию в браузере и вы хотите, чтобы она больше походила на ссылку или другой элемент пользовательского интерфейса, вы можете добиться этого с помощью CSS. Узнайте больше о написании доступной разметки.
Распространение событий
Обработчики событий также будут перехватывать события от любых дочерних элементов вашего компонента. Мы говорим, что событие «проникает» или «распространяется» вверх по дереву: оно начинается с места, где произошло событие, а затем проходит вверх по дереву.
Этот <div> содержит две кнопки. И <div>, и каждая кнопка имеют свои собственные обработчики onClick. Как вы думаете, какие обработчики сработают, когда вы нажмете на кнопку?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <button onClick={() => alert('Playing!')}> Play Movie </button> <button onClick={() => alert('Uploading!')}> Upload Image </button> </div> );}Если вы нажмете на любую из кнопок, сначала сработает ее onClick, а затем — onClick родительского элемента <div>. Таким образом, появится два сообщения. Если вы нажмете на саму панель инструментов, сработает только onClick родительского элемента <div>.
Все события распространяются в React, за исключением onScroll, которое работает только на том теге JSX, к которому вы его прикрепили.
Остановка распространения
Обработчики событий получают объект события в качестве единственного аргумента. По соглашению он обычно называется 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('Playing!')}> Play Movie </Button> <Button onClick={() => alert('Uploading!')}> Upload Image </Button> </div> );}Когда вы нажимаете на кнопку:
- React вызывает обработчик
onClick, переданный в<button>. - Этот обработчик, определенный в
Button, выполняет следующее:- Вызывает
e.stopPropagation(), предотвращая дальнейшее распространение события. - Вызывает функцию
onClick, которая является пропсом, переданным из компонентаToolbar.
- Вызывает
- Эта функция, определённая в компоненте
Toolbar, отображает собственное предупреждение кнопки. - Поскольку распространение события было остановлено, обработчик
onClickродительского элемента<div>не запускается.
В результате вызова e.stopPropagation() нажатие на кнопки теперь отображает только одно предупреждение (от <button>), а не два (от <button> и родительской панели инструментов <div>). Нажатие на кнопку — это не то же самое, что нажатие на окружающую панель инструментов, поэтому остановка распространения имеет смысл для этого интерфейса.
События фазы захвата
В редких случаях вам может понадобиться перехватить все события на дочерних элементах, даже если они остановили распространение. Например, возможно, вы хотите регистрировать каждый клик в аналитике, независимо от логики распространения. Вы можете сделать это, добавив Capture в конец имени события:
<div onClickCapture={() => { /* это выполняется первым */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>Каждое событие распространяется в три этапа:
- Оно проходит вниз, вызывая все обработчики
onClickCapture. - Оно запускает обработчик
onClickнажатого элемента. - Оно проходит вверх, вызывая все обработчики
onClick.
События Capture полезны для кода, такого как маршрутизаторы или аналитика, но вы, вероятно, не будете использовать их в коде приложения.
Передача обработчиков в качестве альтернативы распространению
Обратите внимание, как этот обработчик клика выполняет строку кода а затем вызывает свойство onClick, переданное родительским элементом:
function Button({ onClick, children }) {
return (
<button onClick= {e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}Вы также можете добавить дополнительный код в этот обработчик перед вызовом обработчика события onClick родительского компонента. Этот паттерн представляет собой альтернативу распространению. Он позволяет дочернему компоненту обрабатывать событие, одновременно позволяя родительскому компоненту задавать некоторое дополнительное поведение. В отличие от распространения, это не происходит автоматически. Но преимущество этого шаблона в том, что вы можете чётко проследить всю цепочку кода, который выполняется в результате какого-либо события.
Если вы полагаетесь на распространение событий и вам сложно отследить, какие обработчики выполняются и почему, попробуйте вместо этого этот подход.
Предотвращение поведения по умолчанию
Некоторые события браузера имеют связанное с ними поведение по умолчанию. Например, событие отправки <form>, которое происходит при нажатии кнопки внутри него, по умолчанию перезагрузит всю страницу:
export default function Signup() { return ( <form onSubmit={() => alert('Submitting!')}> <input /> <button>Send</button> </form> );}Вы можете вызвать e.preventDefault() на объекте события, чтобы предотвратить это:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Submitting!'); }}> <input /> <button>Send</button> </form> );}Не путайте e.stopPropagation() и e.preventDefault(). Обе функции полезны, но не связаны между собой:
e.stopPropagation()предотвращает срабатывание обработчиков событий, привязанных к тегам выше.e.preventDefault()предотвращает стандартное поведение браузера для тех немногих событий, у которых оно есть.
Могут ли обработчики событий иметь побочные эффекты?
Конечно! Обработчики событий — лучшее место для побочных эффектов.
В отличие от функций рендеринга, обработчики событий не обязательно должны быть чистыми, поэтому это отличное место для изменения чего-либо — например, изменения значения поля ввода в ответ на ввод текста или изменения списка в ответ на нажатие кнопки. Однако, чтобы изменить какую-то информацию, сначала нужно иметь способ её хранения. В React это делается с помощью state, памяти компонента. Вы узнаете об этом всё на следующей странице.
Вывод
- Вы можете обрабатывать события, передавая функцию в качестве проп элемента, например
<button>. - Обработчики событий должны передаваться, а не вызываться!
onClick={handleClick}, а неonClick={handleClick()}. - Вы можете определить функцию обработчика события отдельно или в строке.
- Обработчики событий определяются внутри компонента, поэтому они могут получить доступ к проп.
- Вы можете объявить обработчик события в родительском компоненте и передать его в качестве пропса дочернему.
- Вы можете определить собственные пропсы обработчиков событий с именами, специфичными для приложения.
- События распространяются вверх. Вызовите
e.stopPropagation()на первом аргументе, чтобы предотвратить это. - События могут иметь нежелательное поведение браузера по умолчанию. Вызовите
e.preventDefault(), чтобы предотвратить это. - Явное вызов проп обработчика события из дочернего обработчика — хорошая альтернатива распространению.
Добавление
Некоторые
Состояние. Память компонента
Компонентам часто приходится изменять содержимое экрана в результате взаимодействия с пользователем. Ввод текста в форму должен обновлять поле ввода, нажатие кнопки «Далее» в карусели изображений — менять отображаемое изображение, а нажатие кнопки «Купить» — добавлять товар в корзину. Компонентам необходимо «запоминать» вещи - текущее значение ввода, текущее изображение, корзину. В React такая специфическая для компонента память называется *состоянием*.