Определение
Публикация-подписка — это шаблон обмена сообщениями, при котором отправители сообщений, называемые издателями, не программируют сообщения для отправки напрямую конкретным получателям, называемым подписчиками, а вместо этого классифицируют опубликованные сообщения по классам, не зная, какие подписчики, если таковые имеются, может быть. Точно так же подписчики проявляют интерес к одному или нескольким классам и получают только те сообщения, которые представляют интерес, не зная, какие издатели, если таковые имеются, существуют.
Звучит сложно и непонятно, но на самом деле все очень просто. С подобными механизмами мы сталкиваемся чуть ли не каждый день в процессе работы. Чтобы приблизить и прояснить, как это выглядит, давайте сначала рассмотрим простой пример.
button.addEventListener("click", () => console.log("Hello"));
Мы подключили слушателя к кнопке. Когда пользователь нажимает на нее, консоль печатает «Hello». Что означает этот щелчок? Ну, это представляет собой событие. Пользователь может навести курсор (также событие) на кнопку, но ничего не произойдет. Только если он нажмет. Так устроен слушатель. Слушать только нажатия кнопок.
В отличие от события клика, которое связано с кнопкой, шаблон PubSub обеспечивает возможность того, что один компонент генерирует событие, а другой получает результат этого события и реагирует в соответствии со своей логикой.

Пример: https://codesandbox.io/s/design-pattern-pubsub-2vds9z
В примере мы видим, что, нажав одну кнопку, мы получаем всплывающее сообщение. Примечание: это относится только к первым 4 кликам. Я объясню почему позже.
Класс PubSub
interface Subscribers {
[key: string]: Array<(data: any) => void>;
}
let subscribers: Subscribers = {};
export default {
publish(event: string, data: Object) {
if (!subscribers[event]) {
return;
}
subscribers[event].forEach((subscriberCallback) =>
subscriberCallback(data)
);
},
subscribe(event: string, callback: (data: any) => void) {
let index: number;
if (!subscribers[event]) {
subscribers[event] = [];
}
index = subscribers[event].push(callback) - 1;
return {
unsubscribe() {
subscribers[event].splice(index, 1);
}
};
}
};
- подписчики — представляет объект, который содержит события в виде свойств, а значение свойства представляет собой массив функций (методов), которые будут выполняться при возникновении этого события. В нашем случае
interface Subscribers {
[key: string]: Array<(data: any) => void>;
}
subscribers = {
showToastMessage: [
function handler() {},
]
}
Как видите, путем трансляции этого события можно запустить n-е количество функций, поэтому следует быть осторожным, когда использовать этот паттерн и как называть события. Рекомендуется делать это с плейсхолдерами, а не напрямую со строками, как в случае с «кликом».
- publish — метод, который публикует/транслирует событие с определенной информацией (payload — опционально). Принимает все зарегистрированные методы, выполняет их, передает им данные (полезную нагрузку)
кнопки.ts
showMessage = () => {
const { type, text } = this.button.dataset;
pubSub.publish(SHOW_TOAST_MESSAGE, {
type,
text
});
};
- подписка — метод, используемый компонентом, который хочет отреагировать на определенное событие.
тосты.ts
this.event = pubSub.subscribe(SHOW_TOAST_MESSAGE, this.handler);
По сути, он говорит: когда произойдет это событие (SHOW_TOAST_MESSAGE), выполните эту функцию (обработчик).
Компонент кнопок генерирует событие.
Компонент toasts получает полезную нагрузку и реагирует на событие.
Ранее я упоминал выше, что только первые четыре клика будут отображать всплывающее сообщение. Сейчас объясню почему.
PubSub отслеживает событие, но также предоставляет возможность прекратить отслеживание события в какой-то момент. Вот почему мы сохранили подписку в this.event, потому что она возвращает метод отмены подписки, который позволяет нам перестать следить за событием в данный момент.
if (this.counter === 4) {
this.event.unsubscribe();
}
Когда количество кликов достигнет 4, прекратите отслеживать нажатия кнопок.
Пример: https://codesandbox.io/s/pub-sub-pattern-video-players-93wmq2

В этой ситуации у нас есть несколько компонентов, которые отправляют событие (я проигрываю видео), и один компонент подписался на это событие, чтобы он мог контролировать, какие видеофайлы он должен останавливать, чтобы избежать коллизии.
Заключение
- слабая связь — связь компонентов через посредника. Классы не должны хранить логику или состояние друг друга.
— publish — отправляет, выдает результат/данные
— subscribe — регистрирует callback-функции, которые будут выполняться при генерации определенного события - простота разработки — всего один класс с двумя методами, которые управляют общением
- общение в режиме реального времени — отправка событий и выполнение происходит немедленно
- масштабируемость и надежность — нам не нужно беспокоиться о количестве издателей и подписчиков. Логика добавления создана и действительна для всех проектов. общение простое, а логика отдельная. Если необходимо что-то отладить, это должно быть связано с логикой выполнения, расположенной на подписчике, или полезной нагрузкой на издателе.
- Мы можем использовать его в ситуациях, чтобы отправить всплывающее сообщение, когда пользователь вошел в систему, или остановить воспроизведение одного носителя (аудио, видео), потому что мы запустили новый носитель. Нам не нужно, чтобы видеофайл и, например, аудиофайл воспроизводились одновременно. Когда пользователь загружает файл неправильного типа, вы можете показать всплывающее сообщение, …
- Если бы все было идеально, этот подход применялся бы везде. Однако у него есть несколько недостатков.
— Он глобальный. Это не просто связь одного компонента с другим. Это событие, на которое могут реагировать n компонентов. И это число может быстро расти.
— Количество подписчиков и издателей произвольно. Представьте, что у такого события, как наше, есть еще десять функций, которые будут выполняться, когда пользователь нажимает на кнопку. Какова вероятность коллизии в логике, кто что и когда должен делать?
— Не пишите прямо название события, поставьте его как заполнитель.
const SHOW = 'showSomething';
- Издатель не знает о подписчике, так же как подписчик не знает об издателе.