Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Event-Driven Architecture w React -- Luzna komunikacja

Na statku kosmicznym rozne systemy musza sie komunikowac: silnik informuje o zmianie predkosci, oslony raportuja uszkodzenia, system nawigacji aktualizuje kurs. Ale te systemy nie znaja sie nawzajem -- komunikuja sie przez centralny system zdarzen. To wlasnie Event-Driven Architecture -- wzorzec, w ktorym komponenty komunikuja sie przez zdarzenia zamiast bezposrednich zaleznosci.

Czym jest Event-Driven Architecture?

Event-Driven Architecture (EDA) polega na:

  1. Emitowaniu zdarzen -- komponent oglosza, ze cos sie stalo
  2. Nasluchiwaniu zdarzen -- inne komponenty reaguja na te ogloszenia
  3. Luznym powiazaniu -- nadawca nie wie, kto nasluchuje; odbiorca nie wie, kto emituje

EventBus -- centralny magistrala zdarzen

1// Prosty EventBus z typowanymi zdarzeniami
2class EventBus {
3  constructor() {
4    this.listeners = new Map();
5  }
6
7  on(event, callback) {
8    if (!this.listeners.has(event)) {
9      this.listeners.set(event, new Set());
10    }
11    this.listeners.get(event).add(callback);
12
13    // Zwroc funkcje cleanup
14    return () => {
15      this.listeners.get(event)?.delete(callback);
16    };
17  }
18
19  emit(event, payload) {
20    if (this.listeners.has(event)) {
21      this.listeners.get(event).forEach(cb => cb(payload));
22    }
23  }
24
25  off(event, callback) {
26    this.listeners.get(event)?.delete(callback);
27  }
28}
29
30// Singleton -- jeden bus dla calej aplikacji
31const eventBus = new EventBus();

Custom hooki do obslugi zdarzen

1// Hook do nasluchiwania zdarzen
2function useEventListener(event, handler) {
3  const savedHandler = useRef(handler);
4
5  useEffect(() => {
6    savedHandler.current = handler;
7  }, [handler]);
8
9  useEffect(() => {
10    const listener = (payload) => savedHandler.current(payload);
11    const unsubscribe = eventBus.on(event, listener);
12    return unsubscribe; // Cleanup przy unmount
13  }, [event]);
14}
15
16// Hook do emitowania zdarzen
17function useEventEmitter() {
18  return useCallback((event, payload) => {
19    eventBus.emit(event, payload);
20  }, []);
21}

Praktyczny przyklad: System alertow statku

1// Komponent silnika -- emituje zdarzenia
2function EnginePanel() {
3  const emit = useEventEmitter();
4
5  const setSpeed = (speed) => {
6    emit('engine:speedChanged', { speed, timestamp: Date.now() });
7    emit('ship:log', { source: 'Engine', message: `Speed: ${speed}` });
8  };
9
10  return (
11    <div className="panel">
12      <h3>Silnik</h3>
13      <button onClick={() => setSpeed('MAX')}>MAX</button>
14      <button onClick={() => setSpeed('CRUISE')}>CRUISE</button>
15      <button onClick={() => setSpeed('STOP')}>STOP</button>
16    </div>
17  );
18}
19
20// Komponent wyswietlacza -- nasluchuje zdarzen
21function SpeedDisplay() {
22  const [speed, setSpeed] = useState('IDLE');
23
24  useEventListener('engine:speedChanged', (data) => {
25    setSpeed(data.speed);
26  });
27
28  return <div className="display">Predkosc: {speed}</div>;
29}
30
31// Komponent logu -- nasluchuje wszystkich logow
32function ShipLog() {
33  const [entries, setEntries] = useState([]);
34
35  useEventListener('ship:log', (data) => {
36    setEntries(prev => [
37      { ...data, time: new Date().toLocaleTimeString() },
38      ...prev,
39    ].slice(0, 10));
40  });
41
42  return (
43    <ul className="log">
44      {entries.map((entry, i) => (
45        <li key={i}>[{entry.time}] {entry.source}: {entry.message}</li>
46      ))}
47    </ul>
48  );
49}

Zwroc uwage --

EnginePanel
nie importuje
SpeedDisplay
ani
ShipLog
. Komponenty sa calkowicie niezalezne. Mozesz usunac
ShipLog
z drzewa komponentow i
EnginePanel
dalej dziala bez zmian.

Natywne EventTarget API

Zamiast wlasnego EventBus, mozesz uzyc natywnego

EventTarget
API przegladarki:

1// EventTarget -- wbudowane w przegladarke
2const shipEvents = new EventTarget();
3
4function useNativeEventListener(eventName, handler) {
5  useEffect(() => {
6    const listener = (e) => handler(e.detail);
7    shipEvents.addEventListener(eventName, listener);
8    return () => shipEvents.removeEventListener(eventName, listener);
9  }, [eventName, handler]);
10}
11
12function emitNativeEvent(eventName, detail) {
13  shipEvents.dispatchEvent(new CustomEvent(eventName, { detail }));
14}

Zaawansowany EventBus z middleware

Mozna rozszerzyc EventBus o middleware -- funkcje przechwytujace zdarzenia przed dostarczeniem:

1class AdvancedEventBus extends EventBus {
2  constructor() {
3    super();
4    this.middleware = [];
5  }
6
7  use(middlewareFn) {
8    this.middleware.push(middlewareFn);
9  }
10
11  emit(event, payload) {
12    // Przepusc przez middleware
13    let finalPayload = payload;
14    for (const mw of this.middleware) {
15      finalPayload = mw(event, finalPayload);
16      if (finalPayload === null) return; // Middleware zablokowalo zdarzenie
17    }
18    super.emit(event, finalPayload);
19  }
20}
21
22// Middleware: logowanie
23advancedBus.use((event, payload) => {
24  console.log(`[Event] ${event}`, payload);
25  return payload;
26});
27
28// Middleware: walidacja
29advancedBus.use((event, payload) => {
30  if (event === 'engine:speedChanged' && payload.speed === 'MAX') {
31    if (!window.confirm('Na pewno pelna moc?')) return null; // Zablokuj!
32  }
33  return payload;
34});

EDA vs Context API vs Props

| Cecha | Props | Context API | Event-Driven | |-------|-------|-------------|-------------| | Kierunek | Parent -> Child | Ancestor -> Descendants | Dowolny | | Powiazanie | Scisle | Srednie | Luzne | | Rerendery | Bezposrednie | Wszystkie konsumenci | Tylko subskrybenci | | Komunikacja cross-branch | Niemozliwa | Wymaga wspolnego przodka | Bezposrednia | | Debugowalnosc | Latwa | Srednia | Wymaga logowania |

Kiedy uzywac Event-Driven Architecture?

  • Komunikacja miedzy odleglymi komponentami -- nie chcesz przekazywac propsow przez 5 poziomow
  • System powiadomien/alertow -- wiele zrodel generuje eventy, jeden komponent je wyswietla
  • Logowanie i analytics -- zdarzenia uzytkownika emitowane raz, nasluchiwane przez wiele systemow
  • Real-time updates -- dane aktualizowane w czasie rzeczywistym (WebSocket -> EventBus -> komponenty)

EDA to jak centralny system komunikacji na statku kosmicznym. Kazdy modul moze nadawac i odbierac wiadomosci bez bezposredniego polaczenia z innymi modulami. Klucz do sukcesu to pamietanie o cleanup (odsubskrybowaniu) przy odmontowaniu komponentow -- inaczej powstana wycieki pamieci, jak uszkodzony przekaznik, ktory nadal zuzywa energie!

Vai a CodeWorlds