Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы разбиваем карточку товара на несколько уровней компонентов, создавая чёткую иерархию.
Мы выносим отдельную миниатюру в свой компонент, потому что это самая маленькая единица галереи. Thumbnail принимает объект изображения, флаг активности и функцию клика. Условный класс active выделяет текущую выбранную миниатюру. Это позволяет стилизовать каждую миниатюру независимо и упрощает код списка в родительском компоненте.
Gallery объединяет главное изображение и список миниатюр. Мы выделили галерею в отдельный компонент, потому что она имеет собственную логику отображения - главное фото и навигация. Компонент использует Thumbnail для каждой миниатюры, передавая проверку активности currentImage === image.src. Галерея получает функцию onImageClick и пробрасывает её в миниатюры - это паттерн делегирования события наверх.
ProductHeader отвечает за отображение названия, цены и рейтинга. Мы используем фрагмент <>...</> вместо обёртки div, потому что эти элементы не нуждаются в дополнительном контейнере. Вынесение заголовка в отдельный компонент позволяет легко изменять форматирование цены или звёздочек рейтинга, не затрагивая остальной код страницы товара.
QuantitySelector - это контролируемый компонент для выбора количества товара. Мы выделили его отдельно, потому что селектор количества - это переиспользуемый UI элемент, который может понадобиться в корзине или других местах. Компонент получает значение и обработчик изменения, но не содержит логику валидации - она остаётся в родительском компоненте App.
AddToCartButton управляет отображением кнопки в двух состояниях - обычном и после добавления. Мы вынесли кнопку в отдельный компонент, потому что она имеет специфичное поведение - изменение текста, класса и блокировка при добавлении. Компонент получает флаг inCart и на его основе меняет внешний вид, но логика таймера остаётся в App.
ProductDescription - это простой презентационный компонент, который только отображает текст. Даже такой простой блок полезно выделить, потому что в будущем описание может стать сложнее - добавятся списки характеристик, вкладки с отзывами, галерея деталей.
ProductInfo - это контейнер для правой части карточки товара. Он собирает вместе заголовок, селектор количества, кнопку и описание. Мы создали этот промежуточный компонент, чтобы отделить информационную часть от галереи изображений. ProductInfo пробрасывает props в дочерние компоненты - quantity идёт в QuantitySelector, inCart в AddToCartButton.
В App мы видим структуру верхнего уровня - галерея слева, информация справа. Все состояние (currentImage, inCart, quantity) и обработчики остались в App, потому что эти данные нужны разным частям приложения. App не содержит деталей реализации - он только композирует два больших блока и управляет их взаимодействием через состояние.
Логика валидации остаётся в App, а не в QuantitySelector. Это правильно, потому что родительский компонент знает бизнес-правила - минимум 1, максимум 10. QuantitySelector - это просто UI элемент, который не должен содержать бизнес-логику. Math.max(1, Math.min(10, value)) ограничивает значение диапазоном 1-10.
При добавлении в корзину мы временно устанавливаем inCart в true, а через 2 секунды возвращаем в false. Эта логика находится в App, потому что она управляет состоянием. Компонент AddToCartButton только отображает результат - он не знает про таймер, он просто реагирует на изменение prop inCart.