Obsługa zdarzeń
React pozwala nam dodać procedury obsługi zdarzeń (ang. event handlers) do naszego JSX. Procedury obsługi zdarzeń to twoje własne funkcje, które zostaną wywołane w odpowiedzi na interakcje tj. klikanie, najeżdżanie, wybieranie pól tekstowych itp.
W tej sekcji dowiesz się
- Na jakie sposoby można pisać procedury obsługi zdarzeń
- Jak przekazać logikę obsługi zdarzeń z komponentu rodzica
- Jak zdarzenia są przekazywane i jak je powstrzymać
Dodawanie procedur obsługi zdarzeń
Aby dodać procedurę obsługi zdarzeń, najpierw zdefiniuj funkcję a następnie przekaż ją jako właściwość (ang. prop) do odpowiedniejgo tagu JSX. Na przykład, oto przycisk, który jeszcze nic nie robi:
export default function Button() { return ( <button> Nic nie robię! </button> ); }
Możesz sprawić, aby pokazywał wiadomość po kliknięciu go przez użytkownika, w tych trzech krokach:
- Zadeklaruj funkcję
handleClick
wewnątrz twojego komponentuButton
. - Zaimplementuj logikę wewnątrz tej funkcji (użyj
alert
by pokazać wiadomość). - Dodaj
onClick={handleClick}
do JSXa<button>
.
export default function Button() { function handleClick() { alert('Nacisnąłeś mnie!'); } return ( <button onClick={handleClick}> Naciśnij mnie! </button> ); }
Zdefiniowałeś funkcję handleClick
a potem przekazałeś ją jako właściwość do <button>
. handleClick
jest procedurą obsługi zdarzeń. Takie funkcje:
- Są zwykle definiowane wewnątrz komponentów
- Mają nazwy zaczynające się od
handle
, po których następuje nazwa zdarzenia.
Często zauważysz onClick={handleClick}
, onMouseEnter={handleMouseEnter}
itp.
Zamiast tego, możesz zdefiniować procedurę obsługi zdarzeń w lini z JSX:
<button onClick={function handleClick() {
alert('Nacisnąłeś mnie!');
}}>
Lub, zwięźlej, używając funkcji strzałkowej:
<button onClick={() => {
alert('Nacisnąłeś mnie!');
}}>
Każdy z tych styli jest sobie równy. Wewnątrzliniowe procedury są wygodne dla krótkich funkcji
Odczytywanie właściwości w procedurach obsługi zdarzeń
Ponieważ procedury są deklarowane wewnątrz komponentu, mają dostęp do jego właściwości, Oto przycisk, który po kliknięciu pokaże alert z właściwością message
:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Odtwarzanie!"> Odtwórz film </AlertButton> <AlertButton message="Dodawanie!"> Dodaj film </AlertButton> </div> ); }
To pozwala tym dwóm przyciskom pokazywać różne wiadomości. Spróbuj je zmienić.
Przekazywabnie procedur obsługi zdarzeń jako właściwości
Często będziesz chciał, aby komponent-rodzic zdefiniował dziecku procedurę obsługi zdarzeń. Przyjrzyj się przyciskom: w zależności od tego, gdzie użyjesz komponentu Button
, możesz chcieć wykonać inną funkcję - być może jeden odtwarza film, a drugi wrzuca obrazek.
Aby to zrobić, przekaż właściwość, którą komponent otrzymał od rodzica, jako procedura obsługi w taki sposób:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Odtwarzanie ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Odtwórz "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Dodawanie!')}> Dodaj obraz </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> ); }
Tutaj, komponent Toolbar
renderuje PlayBytton
i UploadButton
:
PlayButton
przekazujehandlePlayClick
jako właściwośćonClick
doButton
wewnątrz.UploadButton
przekazuje() => alert('Dodawanie!')
jako właściwośćonClick
doButton
wewnątrz.
W końcu, twój komponent Button
akceptuje właściwość zwaną onClick
. Przekazuje ją bezpośrednio do wbudowanego w przeglądarkę <button>
z onClick={onClick}
. Ten wycinek mówi Reactowi, aby wywołał ją na kliknięcie.
Jeśli używasz systemów projektu, często komponenty tj. przyciski posiadają style, ale nie definiują zachowania. Zamiast tego, komponenty tj. PlayButton
czy UploadButton
będą przekazywały w dół procedury obsługi zdarzeń.
Nazywanie właściwości procedur obsługi zdarzeń
Komponenty wbudowane tj. <button>
i <div>
wspierają jedynie nazwy zdarzeń z przeglądarki jak onClick
. Jednak, gdy budujesz własne komponenty, możesz nazywać ich właściwości procedur obsługi jakkolwiek chcesz.
Według konwencji, właściwości procedur obsługi zdarzeń powinny zaczynać się od on
i wielkiej litery tuż za nim.
Na przykład, właściwość onClick
komponentu Button
mogłaby być nazwana onSmash
:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Odtwarzanie!')}> Odtwórz film! </Button> <Button onSmash={() => alert('Dodawanie!')}> Dodaj zdjęcie </Button> </div> ); }
W tym przykładzie, <button onClick={onSmash}>
pokazuje że przeglądarkowy <button>
(lowercase) nadal potrzebuje właściwości onClick
, ale jej nazwa otrzymana przez twój własny komponent Button
może być jaka chcesz!
Gdy twój komponent wspiera wiele interakcji, możesz nazwać właściwości procedur obsługi zdarzeń dla elementów typowych w twojej aplikacji. Na przykład, ten komponent Toolbar
otrzymuje procedury onPlayMovie
i onUploadImage
:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Odtwarzanie!')} onUploadImage={() => alert('Dodawanie!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Odtwórz Film! </Button> <Button onClick={onUploadImage}> Dodaj Obraz! </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Zwróć uwagę na to, że komponent App
nie musi wiedzień co Toolbar
zrobi z onPlayMovie
lub onUploadImage
. To szczegół w implementacji Toolbar
. Tutaj, Toolbar
przekazuje je niżej jako procedury onClick
do swoich Button
ów, ale później mogą również zostać wywołane skrótem klawiszowym. Nazywanie właściwości po interakcjach specyficznych dla aplikacji tj. onPlayMovie
pozwala ci wygodnie zmieniać w jaki sposób będą później użyte.
Przekazywanie (propagacja) zdarzeń
Procedury obsługi zdarzeń wyłapią również zdarzenia z któregokolwiek komponentu potomnego. Mówimy wtedy, że zdarzenie “bąbelkuje” (ang. “bubbles”) lub “jest przekazywane” (ang. “propagates”) w górę drzewa: Zaczyna się to tam, gdzie zdarzenie miało miejsce, a potem idzie w górę.
Ten <div>
zawiera dwa przyciski. Oba elementy <div>
i każdy przycisk mają swoją własną obsługę onClick
. Jak myślisz, która z nich zostanie uruchomiona gdy naciśniesz przycisk?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('Nasisnąłeś na pasek zadań!'); }}> <button onClick={() => alert('Odtwarzanie!')}> Odtwórz Film! </button> <button onClick={() => alert('Dodawanie!')}> Dodaj Obraz! </button> </div> ); }
Naciskając na którykowliek z przycisków, ich onClick
uruchomi się jako pierwszy. Następnie wykona się onClick
z nadrzędnego <div>
a. Zatem pojawią się 2 wiadomości. Jeśli naciśniesz stricte na pasek, jedynie onClick
<div>
a zostanie uruchomiony
Powstrzymywanie przekazywania
Procedury obsługi zdarzeń otrzymują jako jedyny argument obiekt zdarzenia (ang. event object). Z definicji nazywany jest e
, co pochodzi od angielskiego “event”. Możesz go użyć do odczytu informacji o zdarzeniu
Poza tym, taki obiekt pozwala zatrzymać przekazanie. Jeśli chcesz powstrzymać zdarzenie od dotarcia do komponentu nadrzędnego, musisz wywołać e.stopPropagation()
jak w tym komponencie Button
:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('Nacisnąłeś pasek zadań!'); }}> <Button onClick={() => alert('Odtwarzanie!')}> Odtwórz film! </Button> <Button onClick={() => alert('Dodawanie!')}> Dodaj zdjęcie! </Button> </div> ); }
Gdy naciśniesz przycisk:
- React wywołuje procedurę
onClick
przekazaną do<button>
. - Ta procedura, zdefiniowana w
Button
, wykonuje następujące czynności:- Wywołuje
e.stopPropagation()
, powstrzumując obiekt przed przekazaniem dalej . - Wywołuje funkcję
onClick
, która jest właściwością otrzymaną z komponentuToolbar
.
- Wywołuje
- Ta funkcja, zdefiniowana w komponencie
Toolbar
, wyświetla swój własny alert. - Ponieważ zatrzymaliśmy przekazanie, procedura
onClick
nadrzędnego<div>
a nie uruchamia się.
Jako wynik e.stopPropagation()
, kliknięcie na przyciski pokazuje teraz pojedynczy alert (z <button>
), zamiast 2 (z <button>
i nadrzędnego <div>
a). Naciśnięcie przycisku, to nie to samo co naciśnięcie otaczającego go paska zadań, zatem powstrzymanie przekazania ma sens dla tego interfejsu.
Dla dociekliwych
W niewielu przypadkach, możesz musieć przechwycić wyszystkie zdarzenia elementów podrzędnych, nawet jeśli nie są przekazywane. Na przykład, możesz chcieć zapisać każde kliknięcie w danych analitycznych, bez wzglądu na logikę przekazywań. możesz to zrobić dodając Capture
do końca nazwy zdarzenia:
<div onClickCapture={() => { /* to działa pierwsze */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
Każde zdarzenie jest przekazywane w trzech fazach:
- Podróżuje w dół, wywołując wszystkie procedury
onClickCapture
. - Uruchamia procedurę
onClick
naciśniętego elementu. - Podróżuje w górę, wywołując wszystkie procedury
onClick
.
Przechwytywanie zdarzeń przydaje się przy tworzeniu np. routerów czy analityki, ale prawdopodobnie nie znajdzie szerszego zastosowania w kodzie aplikacji
Przekazywanie procedur obsługi jako alternatywa dla porpagacji
Zauważ, jak ta procedura uruchamia linię kodu, a potem wywołuje właściwość onClick
przekazaną przez rodzica:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
Możesz również dodać więcej kodu do tej procedury, przed wywołaniem nadrzędnego onClick
. Ten wzór pokazuje alternatywę dla propagacji. Pozwala ona komponentowi potomnemu zająć się zdarzeniem, podczas gdy ten nadrzędny może określić jakieś dodatkowe zachowanie. W przeciwieństwie do propagacji, nie jest to automatyczne. Ale plusem tego wzoru jest możliwość czystego podążania za całym ciągiem kody, który wykonuje się jako wynik jakiegoś zdarzenia
Jeśli używając propagacji jest ci ciężko wyśledzić które procedury są wykonywane i dlaczego, spróbuj tego podejścia.
Powstrzymywanie domyślnego zachowania
Niektóre zdarzenia przeglądarki mają domyślne zachowanie powiązane z nim. Np. zdarzenie wysłania formularze, które następuje gdy naciśnięty zostanie przycisk w jego wnętrzu, domyślnie przeładuje całą stronę:
export default function Signup() { return ( <form onSubmit={() => alert('Wysyłanie!')}> <input /> <button>Wyślij</button> </form> ); }
Możesz wywołać e.preventDefault()
z tego obiektu zdarzenia, aby powstrzymać domyślne zachowanie:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Wysyłanie!'); }}> <input /> <button>Wyślij!</button> </form> ); }
Nie myl e.stopPropagation()
i e.preventDefault()
. Oba są użyteczne, ale nie powiązane:
e.stopPropagation()
zatrzymuje procedury obsługi zdarzeń przypisane do tagów wyżej przed uruchomieniem.e.preventDefault()
powstrzymuje domyślne zachowanie przeglądarki dla paru zdarzeń, które je posiadają.
Czy procedury obsługi zdarzeń mają efekty uboczne?
Oczywiście! Procedury obsługi zdarzeń to najlepsze miejsce na efekty uboczne.
W przeciwieństwie do funkcji renderujących, procedury obsługi zdarzeń nie muszą być czyste, więc jest to śwetne miejsce by coś zmienić - na przykład, zmień wartość input’a w odpowiedzi na wpisywanie, lub zmień listę po naciśnięciu przycisku. Jednakże, aby cokolwiek pozmieniać, musisz wpierw jakoś to przechować. W Reakcie, używa się do tego stanu, pamięci komponentu Wszystkiego o tym nauczysz się na następnej stronie.
Powtórka
- Możesz obsługiwać zdarzenia przekacując funkcję jako właściwość do elementu tj.
<button>
. - Procedury obsługi zdarzeń trzeba przekazać, a nie wywołać!
onClick={handleClick}
, nieonClick={handleClick()}
. - Możesz zdefiniować procedurę obsługi zdarzeń osobno, lub w lini.
- Procedury obsługi zdarzeń są wewnątrz komponentu, więc mają dostęp do jego właściwości.
- Możesz stworzyć procedurę obsługi w komponencie nadrzędnym i przekazać ją do podrzędnego.
- Możesz definiować właściwości dla procedur obsługi zdarzeń z nazwami nawiązującymi do aplikacji.
- Zdarzenia przekazywane są do góry. Wywołaj
e.stopPropagation()
, na pierwszym argumencie by temu zapobiec. - Zdarzenia mogą mieć niechciane domyślne zachowania. Wywołaj
e.preventDefault()
by temu zapobiec. - Wywoływanie procedury obsługi zdarzeń od razu z procedury podrzędnej, jest dobrą alternatywą dla przekazywania.
Wyzwanie 1 z 2: Napraw procedurę obsługi zdarzeń
Naciśnięcie tego przycisku powinno zmieniać tło strony między białym a czarnym. Jednak gdy to zrobisz, nic się nie dzieje. Napraw błąd (Nie przejmuj się logiką wewnątrz handleClick
- ta część jest w porządku)
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()}> Przełącz światło </button> ); }