Реагування на події
React надає вам можливість додавати обробники подій до вашого JSX. Обробники подій — це ваші власні функції, які виконуватимуться у відповідь на різні взаємодії, як-от натискання мишкою, наведення курсора, фокусування в елементі введення даних у формі тощо.
You will learn
- Різні способи написання обробника подій
- Як передати логіку обробки подій від батьківського компонента
- Поширення подій і як це зупинити
Додавання обробників подій
Щоб додати обробник подій, спочатку визначте функцію, а потім передайте її як проп у відповідний JSX-тег. Наприклад, ось кнопка, яка ще нічого не робить:
export default function Button() { return ( <button> Я нічого не роблю </button> ); }
Ви можете налаштувати показ повідомлення, коли користувач натискає на неї, виконавши ці три кроки:
- Оголосіть функцію з назвою
handleClick
усередині вашого компонентаButton
. - Реалізуйте логіку всередині цієї функції (використовуйте
alert
, щоб показати повідомлення). - Додайте
onClick={handleClick}
до<button>
JSX.
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
доButton
.UploadButton
передає() => alert('Завантажується!')
як проп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('Відтворюється!')}> Відтворити фільм </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
своїм компонентам Button
, але він також може викликати їх потім у відповідь на певне сполучення клавіш. Іменування пропсів за типом взаємодії з програмою, як-от onPlayMovie
, дає вам можливість змінити спосіб їх використання пізніше.
Поширення події
Обробники подій також будуть реагувати на події від будь-яких дочірніх компонентів вашого компонента. Ми говоримо, що подія “спливає” (“bubbles”) або “поширюється” (“propagates”) деревом: вона починається з того місця, де відбулася подія, а потім піднімається деревом.
Цей <div>
містить дві кнопки. <div>
і кожна кнопка мають власні обробники onClick
. Як ви думаєте, які обробники спрацюють, коли ви натиснете на кнопку?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <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>
із батьківської панелі інструментів). Натискання на кнопку — це не те саме, що натискання на панель інструментів довкола, тому для цього UI розумно зупинити поширення.
Deep Dive
У рідкісних випадках вам може знадобитися перехопити всі події для дочірніх елементах, навіть якщо вони зупинили поширення. Наприклад, можливо, ви хочете додавати кожен клік до аналітики незалежно від логіки поширення. Це можна зробити, додавши Capture
у кінці назви події:
<div onClickCapture={() => { /* цей код виконується спочатку */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
Поширення кожної події складається із трьох фаз:
- Вона рухається вниз, викликаючи всі обробники
onClickCapture
— занурення. - Викликає обробник
onClick
елемента, на який натиснули. - Вона рухається вгору, викликаючи всі обробники
onClick
— спливання.
Події у фазі занурення корисні для навігації (routers) чи аналітики, але ви, ймовірно, не будете використовувати їх безпосередньо у застосунку.
Передавання обробників як альтернатива поширенню
Зверніть увагу, як цей обробник кліків виконує рядок коду, а потім викликає проп 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()
зупиняє виконання обробників подій, доданих до тегів вище деревом.e.preventDefault()
запобігає стандартній поведінці браузера для кількох подій, які її мають.
Чи можуть обробники подій мати побічні ефекти?
Звісно! Обробники подій — найкраще місце для побічних ефектів (side effects).
На відміну від функцій рендерингу, обробники подій не обов’язково мають бути чистими, тому це чудове місце, щоб щось змінити — наприклад, змінити значення у полі введення у відповідь на друкування або змінити список у відповідь на натискання на кнопку. Однак для того, щоб змінити деяку інформацію, вам спочатку потрібен спосіб її зберігання. У React це можна зробити за допомогою стану — пам’яті компонента. Ви дізнаєтеся про все це на наступній сторінці.
Recap
- Ви можете обробляти події, передавши функцію як проп до елемента на зразок
<button>
. - Необхідно передавати обробники подій, а не викликати їх!
onClick={handleClick}
, а неonClick={handleClick()}
. - Ви можете визначити функцію обробника подій окремо або всередині JSX.
- Обробники подій визначені всередині компонента, тому вони можуть отримати доступ до пропсів.
- Ви можете оголосити обробник події у батьківському елементі та передати його як проп дочірньому.
- Ви можете визначити власні пропси обробників подій із назвами, що відповідають меті їхнього застосування.
- Події поширюються (спливають) вгору. Викличте
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> ); }