При активной работе интерфейса — частых запросах к серверу, массовых действиях пользователя или потоковых событиях — возникает ситуация, когда однотипные уведомления начинают накапливаться. Это приводит к визуальному шуму, снижению читаемости интерфейса и ухудшению пользовательского опыта.
Типичные примеры:
Группировка уведомлений решает эту проблему, объединяя однотипные сообщения в одно, с обновлением содержимого или счетчика.
Библиотека Toastify не содержит встроенного механизма группировки, поэтому реализация строится на уровне логики приложения.
Основная идея:
Простейший способ — использовать объект или Map для хранения активных уведомлений.
const activeToasts = new Map();
Для группировки необходимо определить, какие уведомления считаются однотипными. Это достигается через уникальный ключ.
Примеры ключей:
"save-success""network-error""item-added"Ключ формируется на основе типа события и, при необходимости, дополнительных параметров.
function getToastKey(type, context) {
return `${type}-${context || ''}`;
}
Логика:
function showGroupedToast(key, message) {
if (activeToasts.has(key)) {
const toast = activeToasts.get(key);
toast.element.textContent = message;
} else {
const toast = Toastify({
text: message,
duration: 3000,
close: true,
gravity: "top",
position: "right",
stopOnFocus: true,
callback: function() {
activeToasts.delete(key);
}
});
toast.showToast();
activeToasts.set(key, toast);
}
}
Более продвинутый вариант — отображение количества повторений.
Хранение состояния:
const toastCounters = new Map();
Логика обновления:
function showCountingToast(key, baseMessage) {
let count = toastCounters.get(key) || 0;
count++;
toastCounters.set(key, count);
const message = `${baseMessage} (${count})`;
if (activeToasts.has(key)) {
const toast = activeToasts.get(key);
toast.element.textContent = message;
} else {
const toast = Toastify({
text: message,
duration: 3000,
close: true,
gravity: "top",
position: "right",
callback: function() {
activeToasts.delete(key);
toastCounters.delete(key);
}
});
toast.showToast();
activeToasts.set(key, toast);
}
}
При повторных событиях важно не только обновлять текст, но и продлевать отображение уведомления.
Проблема: Toastify не предоставляет встроенного метода “reset таймера”.
Решение:
function refreshToast(key, message) {
if (activeToasts.has(key)) {
const oldToast = activeToasts.get(key);
oldToast.hideToast();
activeToasts.delete(key);
}
const toast = Toastify({
text: message,
duration: 3000,
gravity: "top",
position: "right",
callback: function() {
activeToasts.delete(key);
}
});
toast.showToast();
activeToasts.set(key, toast);
}
Часто удобно группировать уведомления по категориям:
success)error)warning)info)function showTypedToast(type, message) {
const key = `toast-${type}`;
showCountingToast(key, message);
}
Это позволяет:
Иногда требуется более гибкая логика:
Все уведомления одного типа объединяются:
key = "error";
Учитывается контекст:
key = `error-${endpoint}`;
Каждое уведомление уникально:
key = Date.now();
Дополнительно к группировке можно ограничить максимальное число уведомлений:
const MAX_TOASTS = 3;
function limitToasts() {
if (activeToasts.size >= MAX_TOASTS) {
const firstKey = activeToasts.keys().next().value;
activeToasts.get(firstKey).hideToast();
activeToasts.delete(firstKey);
}
}
Использование:
limitToasts();
showGroupedToast("info", "Новое сообщение");
Альтернативный подход — не обновлять, а выстраивать очередь:
const queue = [];
let isShowing = false;
function processQueue() {
if (queue.length === 0) {
isShowing = false;
return;
}
isShowing = true;
const message = queue.shift();
Toastify({
text: message,
duration: 2000,
callback: processQueue
}).showToast();
}
function enqueueToast(message) {
queue.push(message);
if (!isShowing) {
processQueue();
}
}
На практике часто используется сочетание методов:
Пример:
function smartToast(type, message) {
const key = `toast-${type}`;
limitToasts();
showCountingToast(key, message);
}
Для повышения читаемости:
const message = `<strong>${baseMessage}</strong> (${count})`;
Toastify({
text: message,
escapeMarkup: false
}).showToast();
Важно удалять данные о уведомлениях после их исчезновения:
callback: function() {
activeToasts.delete(key);
toastCounters.delete(key);
}
Без этого:
toast.element1. Повторные ошибки сети
2. Массовые действия (например, загрузка файлов)
3. Системные уведомления
4. Логи в интерфейсе
Возможные улучшения:
Пример debounce:
let timeout;
function debouncedToast(key, message) {
clearTimeout(timeout);
timeout = setTimeout(() => {
showGroupedToast(key, message);
}, 300);
}
Для крупных приложений группировка выносится в отдельный модуль:
class ToastManager {
constructor() {
this.active = new Map();
this.counters = new Map();
}
show(key, message) {
let count = (this.counters.get(key) || 0) + 1;
this.counters.set(key, count);
const text = `${message} (${count})`;
if (this.active.has(key)) {
this.active.get(key).element.textContent = text;
} else {
const toast = Toastify({
text,
duration: 3000,
callback: () => {
this.active.delete(key);
this.counters.delete(key);
}
});
toast.showToast();
this.active.set(key, toast);
}
}
}
Такой подход: