Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Propagacja zdarzeń i zaawansowane wzorce obsługi eventów

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.

Jak zdarzenia propagują się w React

W przeglądarkowym DOMie zdarzenia przechodzą przez trzy fazy:

  1. Capture (przechwytywanie) - zdarzenie schodzi od korzenia dokumentu w dół do elementu docelowego
  2. Target - zdarzenie dociera do elementu, na którym nastąpiło kliknięcie
  3. Bubbling (bąbelkowanie) - zdarzenie wraca w górę, od elementu docelowego do korzenia

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ę:

  1. Kajuta: czujnik aktywowany!
  2. Pokład: sygnał przechodzi przez pokład
  3. Stacja kosmiczna: sygnał dotarł do centrum dowodzenia

Zdarzenie bąbelkuje od dziecka do rodzica - dokładnie odwrotnie niż mogłoby się wydawać!

Zatrzymywanie propagacji - stopPropagation()

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
.

Zapobieganie domyślnemu zachowaniu - preventDefault()

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ń (Event Delegation)

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.

Przekazywanie argumentów do handlerów

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.

Obsługa wielu zdarzeń jednym handlerem

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.

Podsumowanie

Propagacja zdarzeń i zaawansowane wzorce obsługi eventów to kluczowe umiejętności pilota statku React:

  1. Bąbelkowanie - zdarzenia propagują od dziecka do rodzica (domyślnie w React)
  2. stopPropagation() - zatrzymuje propagację do elementów nadrzędnych
  3. preventDefault() - blokuje domyślne zachowanie przeglądarki (np. submit formularza)
  4. Delegacja zdarzeń - jeden handler na rodzicu zamiast wielu na dzieciach
  5. Currying handlerów - czyste przekazywanie argumentów do event handlerów
  6. Computed property names - jeden handler dla wielu powiązanych elementów

Opanowanie tych technik pozwoli ci budować precyzyjne systemy interakcji, gdzie każdy sygnał na statku trafia dokładnie tam, gdzie powinien!

Przejdź do CodeWorlds