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.
Event-Driven Architecture (EDA) polega na:
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();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}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.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}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});| 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 |
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!