← Назад к вопросам

Где лежит шина событий?

2.2 Middle🔥 132 комментариев
#JavaScript Core

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Где лежит шина событий?

Шина событий (Event Bus) — это архитектурный паттерн, который используется для передачи сообщений между компонентами без прямого связывания. Вопрос о его местоположении зависит от архитектуры приложения и выбранного подхода.

Традиционный подход: централизованное хранилище

В классических приложениях (Vue 2, jQuery) шина событий часто находилась на уровне компонента или в отдельном модуле.

// Старый Vue 2 подход
const EventBus = new Vue();

// Компонент A отправляет событие
export default {
  methods: {
    sendMessage() {
      EventBus.$emit('message-sent', { text: 'Привет' });
    }
  }
};

// Компонент B слушает событие
export default {
  mounted() {
    EventBus.$on('message-sent', (message) => {
      console.log('Получено:', message.text);
    });
  }
};

Проблемы с шиной событий

// Проблема 1: утечки памяти
mounted() {
  EventBus.$on('event', this.handler);
  // Забыли отписаться!
}

// Проблема 2: сложная отладка
EventBus.$on('user-update', handler1);
EventBus.$on('user-update', handler2);
EventBus.$on('user-update', handler3);
// Кто вызвал какой handler? Неясно.

// Проблема 3: неясная очередность
EventBus.$emit('init');
// Какие компоненты это услышат? В каком порядке?

Современный подход: State Management

В современных приложениях шина событий заменена на state management (Redux, Vuex, NgRx, Zustand).

// Redux - state как источник истины
const store = createStore({
  state: { message: null },
  reducers: {
    setMessage(state, message) {
      state.message = message;
    }
  }
});

// Компонент A отправляет
dispatch(setMessage('Привет'));

// Компонент B подписывается
const message = useSelector(state => state.message);

Где может находиться шина событий

1. На уровне приложения (App.js / main.ts)

// React
const EventBus = new EventEmitter();

export const EventContext = createContext();

function App() {
  return (
    <EventContext.Provider value={EventBus}>
      <MainApp />
    </EventContext.Provider>
  );
}

// Использование в компонентах
const eventBus = useContext(EventContext);
eventBus.on('event', handler);

2. В отдельном модуле/сервисе

// events.ts - отдельный модуль
import { EventEmitter } from 'events';

export const eventBus = new EventEmitter();

// component-a.ts
import { eventBus } from './events';
eventBus.emit('user-login', user);

// component-b.ts
import { eventBus } from './events';
eventBus.on('user-login', (user) => {
  console.log('Пользователь вошел:', user);
});

3. В контексте (React Context)

const EventContext = createContext();

export function EventProvider({ children }) {
  const [events, dispatch] = useReducer(eventReducer, initialState);

  return (
    <EventContext.Provider value={{ events, dispatch }}>
      {children}
    </EventContext.Provider>
  );
}

// Использование
const { events } = useContext(EventContext);

4. В сервисе (Angular)

// event.service.ts
@Injectable({
  providedIn: 'root'
})
export class EventService {
  private eventSubject = new Subject();
  public event$ = this.eventSubject.asObservable();

  emit(name: string, data: any) {
    this.eventSubject.next({ name, data });
  }

  on(name: string) {
    return this.event$.pipe(
      filter(event => event.name === name),
      map(event => event.data)
    );
  }
}

// component.ts
constructor(private eventService: EventService) {}

ngOnInit() {
  this.eventService.on('user-login').subscribe((user) => {
    console.log('Пользователь:', user);
  });
}

Практический пример: простая шина событий

// eventBus.ts
class EventBus {
  private events = new Map();

  on(eventName, handler) {
    if (!this.events.has(eventName)) {
      this.events.set(eventName, []);
    }
    this.events.get(eventName).push(handler);

    // Возвращаем функцию для отписки
    return () => {
      const handlers = this.events.get(eventName);
      const index = handlers.indexOf(handler);
      if (index > -1) {
        handlers.splice(index, 1);
      }
    };
  }

  emit(eventName, data) {
    if (this.events.has(eventName)) {
      this.events.get(eventName).forEach(handler => {
        handler(data);
      });
    }
  }

  off(eventName) {
    this.events.delete(eventName);
  }

  clear() {
    this.events.clear();
  }
}

export const eventBus = new EventBus();

Использование в React

// App.jsx
import { eventBus } from './eventBus';

export function App() {
  useEffect(() => {
    // Очищаем при размонтировании
    return () => eventBus.clear();
  }, []);

  return <MainContent />;
}

// ComponentA.jsx
function ComponentA() {
  const handleClick = () => {
    eventBus.emit('button-clicked', { timestamp: Date.now() });
  };

  return <button onClick={handleClick}>Нажми</button>;
}

// ComponentB.jsx
function ComponentB() {
  const [clicks, setClicks] = useState(0);

  useEffect(() => {
    // Подписываемся на событие
    const unsubscribe = eventBus.on('button-clicked', (data) => {
      console.log('Кнопку нажали в:', data.timestamp);
      setClicks(c => c + 1);
    });

    // Отписываемся при размонтировании
    return unsubscribe;
  }, []);

  return <div>Клики: {clicks}</div>;
}

Сравнение подходов

Event Bus:

  • Плюсы: простота, слабая связанность
  • Минусы: сложная отладка, утечки памяти, неясный data flow

State Management (Redux, Vuex):

  • Плюсы: централизованное состояние, отладка, data flow ясен
  • Минусы: больше boilerplate кода

Context API (React):

  • Плюсы: простой API, встроено в React
  • Минусы: может вызвать лишние re-renders
RxJS (Angular, React):
  • Плюсы: мощный, функциональный подход
  • Минусы: кривая обучения

Рекомендации

  1. Избегай глобальной шины событий для основного state
  2. Используй state management для бизнес-логики
  3. Используй события для редких, несвязанных событий
  4. Всегда отписывайся от событий при размонтировании
  5. В современных приложениях (2024+) предпочитай state management вместо EventBus

Пример: когда использовать EventBus

// Нужно отправить уведомление из глубокого компонента
const NotificationBus = new EventEmitter();

// deep-component.jsx
function DeepComponent() {
  const handleSuccess = () => {
    NotificationBus.emit('show-notification', {
      type: 'success',
      message: 'Сохранено!'
    });
  };

  return <button onClick={handleSuccess}>Сохранить</button>;
}

// notification-container.jsx
function NotificationContainer() {
  const [notification, setNotification] = useState(null);

  useEffect(() => {
    const unsubscribe = NotificationBus.on('show-notification', (data) => {
      setNotification(data);
      setTimeout(() => setNotification(null), 3000);
    });

    return unsubscribe;
  }, []);

  return notification && <Notification {...notification} />;
}

Вывод

Шина событий может находиться в разных местах в зависимости от архитектуры приложения. В современных приложениях её часто заменяет state management (Redux, Vuex, NgRx), который обеспечивает лучшую отладку и контроль data flow. Если всё же используешь EventBus, размещай его в отдельном модуле и всегда отписывайся от событий при размонтировании компонентов.

Где лежит шина событий? | PrepBro