Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы разбиваем панель уведомлений на множество мелких компонентов, создавая глубокую иерархию.
NotificationsHeader объединяет заголовок со счётчиком и кнопку массового действия. Мы выделили шапку отдельно, потому что это независимый блок интерфейса с собственной логикой - показывать счётчик только если есть непрочитанные, блокировать кнопку если ничего не нужно помечать. Условие unreadCount > 0 && ... показывает скобки со счётчиком только когда есть что показывать.
NotificationIcon инкапсулирует логику выбора иконки по типу. Мы вынесли иконку в компонент, потому что соответствие типа и эмодзи - это отдельная ответственность. Функция getIcon находится внутри компонента, потому что она нужна только здесь. switch проверяет тип и возвращает соответствующий эмодзи.
NotificationContent отвечает за отображение текстовой части уведомления. Мы выделили контент отдельно, чтобы отделить структуру (заголовок, текст, время) от других частей уведомления (иконка, кнопка закрытия). Это простой презентационный компонент, который только показывает переданные данные.
CloseButton - это маленький компонент для кнопки удаления. Мы вынесли её отдельно, потому что она содержит важную логику - e.stopPropagation(). Без этого вызова клик по кнопке всплыл бы к родительскому элементу (уведомлению) и вызвал бы его обработчик. stopPropagation останавливает распространение события, делая кнопку независимой.
NotificationItem собирает вместе иконку, контент и кнопку закрытия. Мы создали этот компонент, чтобы инкапсулировать логику одного уведомления - условный класс для прочитанных/непрочитанных, клик для пометки как прочитанного. Условие !notification.read && onMarkAsRead(notification.id) вызывает функцию только для непрочитанных - прочитанные не реагируют на клик.
Оператор && работает как короткая форма условного выполнения. Если !notification.read истинно (уведомление непрочитанное), выполняется правая часть - вызов функции. Если ложно (уведомление прочитано), ничего не происходит. Это компактнее, чем if (!notification.read) { onMarkAsRead(notification.id) }.
NotificationsList - это контейнер для списка уведомлений. Мы выделили список отдельно, чтобы отделить логику отображения множества элементов от логики одного элемента. Компонент получает функции onMarkAsRead и onRemove и пробрасывает их в каждый NotificationItem.
Функция создаёт новый массив, где у каждого уведомления флаг read установлен в true. Мы используем map для создания нового массива объектов. Spread оператор ...notification копирует все поля уведомления, а затем мы перезаписываем только read: true. Это не мутирует оригинальные объекты, соответствуя правилам React.
Функция изменяет только одно уведомление с нужным id. Для уведомления с совпадающим id создаём новый объект с read: true, для остальных возвращаем оригинальные объекты без изменений. Тернарный оператор проверяет каждое уведомление и решает, модифицировать его или оставить.
Метод filter создаёт новый массив, содержащий только элементы, для которых функция вернула true. Условие notification.id !== id означает "все уведомления, кроме удаляемого". Уведомление с нужным id не попадёт в новый массив - оно удалено из списка.
Мы вычисляем количество непрочитанных уведомлений при каждом рендере. filter((n) => !n.read) отбирает только непрочитанные, length даёт их количество. Это не состояние, а вычисляемое значение - оно автоматически обновляется при изменении массива notifications. Не нужно синхронизировать отдельное состояние для счётчика.
Компонент App собирает шапку и список вместе. Структура максимально простая - два больших блока. Всё состояние и бизнес-логика (функции изменения массива) находятся в App, а компоненты только отображают интерфейс и вызывают переданные функции. Шапка управляет массовыми действиями, список отображает элементы и обрабатывает действия над отдельными уведомлениями.