Lekcja 7: Obsługa Zdarzeń (Event Handling)
Aplikacje React są interaktywne, co oznacza, że muszą reagować na działania użytkownika, takie jak kliknięcia myszą, wpisywanie tekstu, najechanie kursorem itp. Obsługa tych zdarzeń w React jest bardzo podobna do obsługi zdarzeń w standardowym HTML DOM, ale z kilkoma istotnymi różnicami składniowymi i koncepcyjnymi.
Podstawy Obsługi Zdarzeń w React
- Nazewnictwo: Zdarzenia w React są nazywane przy użyciu camelCase, np.
onClick
zamiastonclick
,onMouseEnter
zamiastonmouseenter
. - Przekazywanie Funkcji: Jako obsługę zdarzenia (event handler) przekazujemy funkcję (lub referencję do funkcji), a nie string z kodem JavaScript, jak to czasem bywa w HTML.
- Obiekt Zdarzenia: Funkcje obsługi zdarzeń w React otrzymują syntetyczny obiekt zdarzenia (SyntheticEvent), który jest opakowaniem wokół natywnego obiektu zdarzenia przeglądarki. Zapewnia on spójne działanie zdarzeń we wszystkich przeglądarkach.
- Zapobieganie Domyślnemu Zachowaniu: Aby zapobiec domyślnemu zachowaniu przeglądarki (np. przeładowaniu strony po wysłaniu formularza), musimy jawnie wywołać metodę
preventDefault()
na obiekcie zdarzenia. W HTML wystarczyło zwrócićfalse
z handlera, ale w React to nie zadziała.
Przykłady Obsługi Zdarzeń
Obsługa Kliknięcia (onClick)
import React, { useState } from \'react\';
function ClickButton() {
const [message, setMessage] = useState(\'\');
// Definicja funkcji obsługi zdarzenia
const handleClick = () => {
setMessage(\'Przycisk został kliknięty!\');
console.log(\'Kliknięto!\');
};
return (
<div>
{/* Przekazanie referencji do funkcji jako handlera onClick */}
<button onClick={handleClick}>Kliknij mnie</button>
<p>{message}</p>
</div>
);
}
export default ClickButton;
Możemy również zdefiniować funkcję inline za pomocą funkcji strzałkowej:
function ClickButtonInline() {
return (
<button onClick={() => console.log(\'Kliknięto inline!\')}>
Kliknij mnie (inline)
</button>
);
}
Obsługa Zmiany Wartości (onChange) w Polach Formularza
Zdarzenie onChange
jest kluczowe przy pracy z kontrolowanymi komponentami formularzy (o których więcej w lekcji o formularzach). Pozwala reagować na zmiany wprowadzane przez użytkownika w polach input, textarea, select.
import React, { useState } from \'react\';
function InputExample() {
const [text, setText] = useState(\'\');
// Handler otrzymuje obiekt zdarzenia \'e\'
const handleChange = (e) => {
// Dostęp do wartości pola przez e.target.value
setText(e.target.value);
};
return (
<div>
<input type="text" value={text} onChange={handleChange} placeholder="Wpisz coś..." />
<p>Wpisano: {text}</p>
</div>
);
}
export default InputExample;
Obsługa Wysłania Formularza (onSubmit)
Aby zapobiec domyślnemu przeładowaniu strony przy wysyłaniu formularza, używamy e.preventDefault()
.
import React, { useState } from \'react\';
function SimpleForm() {
const [inputValue, setInputValue] = useState(\'\');
const handleChange = (e) => {
setInputValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault(); // Zapobiegaj domyślnemu przeładowaniu strony
alert(`Wysłano wartość: ${inputValue}`);
setInputValue(\'\'); // Wyczyść pole po wysłaniu
};
return (
<form onSubmit={handleSubmit}>
<input type="text" value={inputValue} onChange={handleChange} />
<button type="submit">Wyślij</button>
</form>
);
}
export default SimpleForm;
Przekazywanie Argumentów do Handlerów Zdarzeń
Czasami chcemy przekazać dodatkowe argumenty do funkcji obsługi zdarzenia, np. identyfikator elementu, który został kliknięty.
Metoda 1: Funkcja Strzałkowa Inline
Możemy użyć funkcji strzałkowej bezpośrednio w JSX, która wywoła nasz handler z dodatkowymi argumentami.
function ButtonList() {
const handleButtonClick = (id) => {
console.log(`Kliknięto przycisk o ID: ${id}`);
};
return (
<div>
<button onClick={() => handleButtonClick(1)}>Przycisk 1</button>
<button onClick={() => handleButtonClick(2)}>Przycisk 2</button>
</div>
);
}
Uwaga: Tworzenie funkcji inline przy każdym renderowaniu może mieć niewielki wpływ na wydajność w bardzo złożonych komponentach lub listach. W większości przypadków jest to jednak akceptowalne.
Metoda 2: Metoda `bind` (mniej popularna w Hookach)
W komponentach klasowych często używano this.handleClick.bind(this, arg1)
. W komponentach funkcyjnych jest to mniej powszechne, preferuje się funkcje strzałkowe.
Metoda 3: Atrybuty `data-*`
Możemy przechowywać dane w atrybutach `data-*` i odczytywać je z `e.target.dataset`.
function ButtonDataList() {
const handleButtonClick = (e) => {
const id = e.target.dataset.id;
console.log(`Kliknięto przycisk o ID: ${id}`);
};
return (
<div>
<button data-id="1" onClick={handleButtonClick}>Przycisk 1</button>
<button data-id="2" onClick={handleButtonClick}>Przycisk 2</button>
</div>
);
}
Syntetyczny Obiekt Zdarzenia (SyntheticEvent)
Jak wspomniano, React dostarcza obiekt `SyntheticEvent`, który normalizuje zachowanie zdarzeń między różnymi przeglądarkami. Ma on ten sam interfejs co natywny obiekt zdarzenia przeglądarki, w tym metody `stopPropagation()` i `preventDefault()`, ale działa identycznie we wszystkich przeglądarkach.
Jeśli z jakiegoś powodu potrzebujesz dostępu do natywnego obiektu zdarzenia, możesz go uzyskać przez właściwość nativeEvent
: e.nativeEvent
.
Ważne: Obiekty `SyntheticEvent` są reużywane ze względów wydajnościowych. Oznacza to, że po zakończeniu działania handlera zdarzenia, właściwości obiektu są zerowane. Jeśli potrzebujesz dostępu do właściwości zdarzenia w sposób asynchroniczny (np. w `setTimeout`), musisz albo zapisać potrzebne właściwości do zmiennej, albo wywołać `e.persist()` (choć ta metoda jest obecnie mniej zalecana na rzecz zapisywania wartości).
Ćwiczenie praktyczne
Pokaż rozwiązanie
// src/ToggleButton.jsx
import React, { useState } from \'react\';
function ToggleButton() {
const [isOn, setIsOn] = useState(false);
const handleClick = () => {
setIsOn(prevIsOn => !prevIsOn);
};
return (
<div>
<button onClick={handleClick}>
{isOn ? \'Wyłącz\' : \'Włącz\'}
</button>
<p>Stan: {isOn ? \'Włączony\' : \'Wyłączony\'}</p>
</div>
);
}
export default ToggleButton;
// W App.jsx
import React from \'react\';
import ToggleButton from \'./ToggleButton\";
function App() {
return (
<div>
<h1>Przełącznik</h1>
<ToggleButton />
</div>
);
}
export default App;
Cel: Stworzyć prosty przełącznik typu "Włącz/Wyłącz".
Kroki:
- Stwórz komponent funkcyjny
ToggleButton
. - Użyj
useState
do przechowywania stanu przełącznika (np.isOn
, boolean, początkowofalse
). - Wyrenderuj przycisk. Tekst na przycisku powinien zależeć od stanu
isOn
(np. "Włącz" gdyfalse
, "Wyłącz" gdytrue
). - Dodaj handler
onClick
do przycisku. - W handlerze
onClick
, przełączaj stanisOn
na przeciwną wartość (użyj formy funkcyjnejsetIsOn(prevIsOn => !prevIsOn)
). - Opcjonalnie: Dodaj paragraf poniżej przycisku, który wyświetla tekst "Stan: Włączony" lub "Stan: Wyłączony" w zależności od stanu
isOn
. - Użyj komponentu
ToggleButton
wApp.jsx
.
Zadanie do samodzielnego wykonania
Stwórz komponent "Mouse Tracker", który:
- Wyświetla aktualne współrzędne kursora myszy (X, Y) wewnątrz diva.
- Używa
useState
do przechowywania obiektu ze współrzędnymi{ x: 0, y: 0 }
. - Używa
useEffect
do dodania nasłuchiwania zdarzeniamousemove
na całym oknie (window
) po zamontowaniu komponentu. - W funkcji obsługi zdarzenia
mousemove
aktualizuje stan współrzędnych na podstawiee.clientX
ie.clientY
. - Implementuje funkcję czyszczącą w
useEffect
, która usuwa nasłuchiwanie zdarzeniamousemove
przy odmontowywaniu komponentu. - Wyświetla współrzędne wewnątrz jakiegoś elementu, np.
<p>X: {coords.x}, Y: {coords.y}</p>
.
FAQ - Obsługa Zdarzeń (Event Handling)
Jaka jest różnica między `onClick` w React a `onclick` w HTML?
Główne różnice to: 1) Nazewnictwo: `onClick` (camelCase) w React vs `onclick` (lowercase) w HTML. 2) Wartość: W React przekazujemy funkcję (`onClick={handleClick}`), a w HTML często string z kodem (`onclick="handleClick()"`) lub bezpośrednio kod JS. 3) Obiekt zdarzenia: React używa `SyntheticEvent` dla spójności między przeglądarkami.
Czy mogę używać `addEventListener` bezpośrednio w komponencie React?
Można, ale zazwyczaj nie jest to zalecane dla zdarzeń na elementach renderowanych przez React. Lepiej używać wbudowanych propsów zdarzeń (np. `onClick`). `addEventListener` jest potrzebny głównie do nasłuchiwania zdarzeń na obiektach globalnych (jak `window` czy `document`) lub elementach poza drzewem Reacta, i należy go używać wewnątrz `useEffect` z odpowiednim czyszczeniem.
Co to jest propagacja zdarzeń (event bubbling) w React?
Podobnie jak w DOM, zdarzenia w React propagują (bąbelkują) w górę drzewa komponentów. Oznacza to, że zdarzenie wywołane na elemencie wewnętrznym może być również obsłużone przez handlery przypisane do jego rodziców. Można zatrzymać propagację, wywołując `e.stopPropagation()` na obiekcie `SyntheticEvent`.
Jak obsłużyć zdarzenia `onMouseEnter` i `onMouseLeave`?
Obsługuje się je analogicznie do `onClick`, przekazując funkcje jako propsy `onMouseEnter` i `onMouseLeave` do elementu JSX, który ma na nie reagować. Te zdarzenia są przydatne do tworzenia efektów hover.
Czy przekazywanie funkcji strzałkowej inline w `onClick` jest złe dla wydajności?
Tworzenie nowej funkcji przy każdym renderowaniu (jak w `onClick={() => doSomething()}`) technicznie może wpłynąć na wydajność, ponieważ funkcja przekazywana jako props będzie inna przy każdym renderowaniu, co może powodować niepotrzebne ponowne renderowanie komponentu dziecka (jeśli używa `React.memo` lub `PureComponent`). W praktyce, dla większości aplikacji ten wpływ jest pomijalny. Optymalizacje (np. `useCallback`) są potrzebne tylko w specyficznych przypadkach.
Jak przekazać sam obiekt zdarzenia do handlera, gdy używam funkcji strzałkowej inline z argumentami?
Obiekt zdarzenia jest automatycznie przekazywany jako pierwszy argument do funkcji inline. Możesz go jawnie przekazać dalej do swojego handlera: `onClick={(e) => handleClick(e, customArg)}`.
Czy wszystkie zdarzenia DOM są dostępne w React?
React opakowuje większość popularnych zdarzeń DOM. Pełną listę obsługiwanych zdarzeń można znaleźć w dokumentacji React. Jeśli potrzebujesz obsłużyć bardzo rzadkie zdarzenie, które nie ma odpowiednika w `SyntheticEvent`, możesz użyć referencji (ref) do elementu DOM i dodać nasłuchiwanie za pomocą `addEventListener` w `useEffect`.