Na pokładzie statku kosmicznego React, zdarzenia nie poruszają się w próżni - podróżują przez hierarchię komponentów, jak sygnał alarmowy, który rozchodzi się od czujnika w maszynowni aż do głównego centrum dowodzenia. Zrozumienie propagacji zdarzeń (event propagation) pozwala precyzyjnie kontrolować, który komponent reaguje na sygnał i kiedy.
W przeglądarkowym DOMie zdarzenia przechodzą przez trzy fazy:
React domyślnie obsługuje zdarzenia w fazie bubbling - czyli zdarzenie "bąbelkuje" od dziecka do rodzica. Wyobraź sobie to jak alarm na statku: czujnik dymu w kajucie (element docelowy) uruchamia się, a sygnał bąbelkuje w górę - najpierw słyszy go komendant pokładu, potem szef inżynierów, a na końcu kapitan statku.
1function SpaceStation() {
2 const handleStationClick = () => {
3 console.log('3. Stacja kosmiczna: sygnał dotarł do centrum dowodzenia');
4 };
5
6 return (
7 <div onClick={handleStationClick} style={{ padding: '40px', background: '#1a1a3e' }}>
8 <h2>Stacja Kosmiczna</h2>
9 <Deck />
10 </div>
11 );
12}
13
14function Deck() {
15 const handleDeckClick = () => {
16 console.log('2. Pokład: sygnał przechodzi przez pokład');
17 };
18
19 return (
20 <div onClick={handleDeckClick} style={{ padding: '30px', background: '#2a2a5e' }}>
21 <p>Pokład główny</p>
22 <Cabin />
23 </div>
24 );
25}
26
27function Cabin() {
28 const handleCabinClick = () => {
29 console.log('1. Kajuta: czujnik aktywowany!');
30 };
31
32 return (
33 <button onClick={handleCabinClick}>
34 Czujnik w kajucie
35 </button>
36 );
37}Po kliknięciu przycisku "Czujnik w kajucie" w konsoli pojawi się:
Zdarzenie bąbelkuje od dziecka do rodzica - dokładnie odwrotnie niż mogłoby się wydawać!
Czasem chcesz, żeby sygnał nie wychodził poza dany moduł statku. Metoda
e.stopPropagation() zatrzymuje bąbelkowanie:1function ControlPanel() {
2 const handlePanelClick = () => {
3 console.log('Panel: to się NIE wywoła');
4 };
5
6 return (
7 <div onClick={handlePanelClick} style={{ padding: '20px' }}>
8 <h3>Panel sterowania</h3>
9 <EmergencyButton />
10 </div>
11 );
12}
13
14function EmergencyButton() {
15 const handleEmergency = (e) => {
16 e.stopPropagation(); // Zatrzymaj propagację!
17 console.log('Przycisk awaryjny: sygnał przechwycony lokalnie');
18 };
19
20 return (
21 <button onClick={handleEmergency}>
22 Przycisk awaryjny
23 </button>
24 );
25}Po kliknięciu "Przycisk awaryjny" tylko handler
handleEmergency zostanie wywołany - zdarzenie nie dotrze do rodzica ControlPanel.Oprócz
stopPropagation() (zatrzymuje bąbelkowanie), istnieje e.preventDefault() - zapobiega domyślnemu zachowaniu przeglądarki. Te dwie metody robią zupełnie różne rzeczy:| Metoda | Działanie | |--------|-----------| |
e.stopPropagation() | Zatrzymuje bąbelkowanie do elementów nadrzędnych |
| e.preventDefault() | Blokuje domyślne zachowanie przeglądarki (np. przesłanie formularza) |1function MissionForm() {
2 const [missionName, setMissionName] = useState('');
3
4 const handleSubmit = (e) => {
5 e.preventDefault(); // Zapobiegaj przeładowaniu strony!
6 console.log('Misja zgłoszona:', missionName);
7 };
8
9 return (
10 <form onSubmit={handleSubmit}>
11 <input
12 value={missionName}
13 onChange={(e) => setMissionName(e.target.value)}
14 placeholder="Nazwa misji"
15 />
16 <button type="submit">Zgłoś misję</button>
17 </form>
18 );
19}Bez
e.preventDefault() formularz spowodowałby przeładowanie strony - domyślne zachowanie przeglądarki dla elementu <form>.Delegacja zdarzeń to wzorzec, w którym jeden handler na rodzicu obsługuje zdarzenia ze wszystkich dzieci. To jak jeden oficer na mostku obsługujący wszystkie sygnały z poszczególnych sektorów zamiast przydzielania osobnego oficera do każdego czujnika:
1function CrewList() {
2 const crew = [
3 { id: 1, name: 'Kapitan Nova', role: 'Dowodzenie' },
4 { id: 2, name: 'Inżynier Astro', role: 'Napęd warpowy' },
5 { id: 3, name: 'Nawigator Ra', role: 'Nawigacja' },
6 { id: 4, name: 'Medyk Luna', role: 'Oddział medyczny' },
7 ];
8
9 // Jeden handler na rodzicu zamiast osobnego na każdym elemencie
10 const handleCrewClick = (e) => {
11 const crewId = e.target.closest('[data-crew-id]')?.dataset.crewId;
12 if (crewId) {
13 const member = crew.find(c => c.id === Number(crewId));
14 console.log('Wybrany członek załogi:', member.name);
15 }
16 };
17
18 return (
19 <ul onClick={handleCrewClick}>
20 {crew.map(member => (
21 <li key={member.id} data-crew-id={member.id}>
22 {member.name} - {member.role}
23 </li>
24 ))}
25 </ul>
26 );
27}Delegacja zdarzeń jest szczególnie wydajna przy dużych listach - zamiast 100 osobnych listenerów masz jeden na rodzicu. React i tak optymalizuje eventy wewnętrznie, ale świadomość tego wzorca pomaga pisać czystszy kod.
Częstym wzorcem jest przekazywanie dodatkowych danych do handlera zdarzenia. Zamiast tworzyć handler bezpośrednio w JSX, użyj funkcji strzałkowej lub stwórz dedykowaną funkcję:
1function MissionSelector() {
2 const missions = ['Apollo', 'Artemis', 'Voyager', 'Orion'];
3 const [selected, setSelected] = useState(null);
4
5 // Wzorzec: funkcja zwracająca handler
6 const handleSelect = (missionName) => () => {
7 setSelected(missionName);
8 console.log('Wybrano misję:', missionName);
9 };
10
11 // Alternatywnie: arrow function w JSX
12 // onClick={() => handleSelect(missionName)}
13
14 return (
15 <div>
16 <h3>Wybierz misję:</h3>
17 {missions.map(mission => (
18 <button key={mission} onClick={handleSelect(mission)}>
19 {mission}
20 </button>
21 ))}
22 {selected && <p>Aktywna misja: {selected}</p>}
23 </div>
24 );
25}Wzorzec
handleSelect(arg) => () => {...} (currying) jest czystszą alternatywą dla inline arrow function. Unikaj tworzenia nowych funkcji w render, gdy to możliwe - pomaga to utrzymać czytelność kodu.Gdy komponent obsługuje wiele powiązanych zdarzeń, możesz użyć jednego handlera z rozróżnieniem po
event.target.name lub event.target.dataset:1function ShipControls() {
2 const [systems, setSystems] = useState({
3 engines: false,
4 shields: false,
5 weapons: false,
6 });
7
8 const handleToggle = (e) => {
9 const systemName = e.target.name;
10 setSystems(prev => ({
11 ...prev,
12 [systemName]: !prev[systemName]
13 }));
14 };
15
16 return (
17 <div>
18 <h3>Systemy statku:</h3>
19 {Object.entries(systems).map(([name, isActive]) => (
20 <label key={name} style={{ display: 'block', margin: '8px 0' }}>
21 <input
22 type="checkbox"
23 name={name}
24 checked={isActive}
25 onChange={handleToggle}
26 />
27 {name}: {isActive ? 'AKTYWNY' : 'WYŁĄCZONY'}
28 </label>
29 ))}
30 </div>
31 );
32}Dzięki
[systemName]: !prev[systemName] (computed property name) jeden handler obsługuje wszystkie checkboxy. Atrybut name na inpucie identyfikuje, który system ma zostać przełączony.Propagacja zdarzeń i zaawansowane wzorce obsługi eventów to kluczowe umiejętności pilota statku React:
Opanowanie tych technik pozwoli ci budować precyzyjne systemy interakcji, gdzie każdy sygnał na statku trafia dokładnie tam, gdzie powinien!