Видеоуроки, интерактивный редактор и сохранение прогресса — бесплатно, сразу после входа.
ВойтиСоздать аккаунт — бесплатноЗакончили урок?
Войдите, чтобы отмечать прогресс
В этом решении мы разбиваем приложение фильтрации товаров на независимые компоненты для каждого элемента управления.
SearchInput - это простой обёрнутый input. Мы выделили его отдельно, чтобы изолировать детали - placeholder, тип поля. Компонент извлекает значение из события e.target.value и передаёт его в функцию onChange. Это упрощает использование - родитель просто передаёт функцию setSearch, не думая о структуре события.
CategorySelect инкапсулирует выпадающий список категорий. Мы вынесли его отдельно, потому что список категорий - это отдельная сущность со своими опциями. В будущем категории можно сделать динамическими, передавая массив через props, и компонент будет генерировать <option> автоматически.
InStockCheckbox объединяет чекбокс и метку в один компонент. Обратите внимание на e.target.checked вместо e.target.value - для чекбоксов нужно читать свойство checked, которое возвращает булево значение. Компонент скрывает эту особенность от родителя - он просто получает true или false.
Filters собирает все элементы управления фильтрацией в одном месте. Мы создали этот контейнер, чтобы сгруппировать связанные элементы. Компонент принимает все значения и сеттеры, и передаёт их дальше в дочерние компоненты. Это промежуточный слой, который организует структуру панели фильтров.
Мы передаём setSearch напрямую в prop onChange. Это работает, потому что SearchInput вызывает onChange(e.target.value) - то есть передаёт строку. А setSearch принимает строку. Сигнатуры совпадают, обёртка не нужна. Это делает код компактнее, чем onChange={(value) => setSearch(value)}.
ProductItem отображает информацию об одном товаре. Мы выделили карточку в компонент, потому что каждый товар имеет одинаковую структуру отображения. Условный класс product.inStock ? 'in-stock' : 'out-of-stock' и текст зависят от наличия товара. Это делает карточку самодостаточной - она знает, как отобразить товар полностью.
ProductGrid отвечает за отображение списка товаров. Мы выделили сетку отдельно, чтобы изолировать логику списка от логики одной карточки. Компонент просто проходит по массиву и создаёт ProductItem для каждого элемента. В будущем здесь может появиться пагинация, сортировка или сообщение "Ничего не найдено".
Вся логика фильтрации остаётся в App. Это бизнес-логика приложения - как именно фильтровать товары. Компоненты Filters и ProductGrid не знают про эту логику - они только отображают интерфейс и получают результаты. Три условия объединены через && - товар должен соответствовать всем фильтрам одновременно.
Приводим название товара и поисковый запрос к нижнему регистру, чтобы поиск был регистронезависимым. Метод includes проверяет, содержится ли подстрока в строке. Если search пустая строка, includes('') всегда возвращает true - все товары проходят этот фильтр.
Если выбрана категория "Все", пропускаем все товары. Иначе проверяем точное совпадение категории товара с выбранной. Оператор || (ИЛИ) означает, что достаточно одного истинного условия.
Если чекбокс не отмечен (inStockOnly === false), то !inStockOnly === true, и фильтр пропускает все товары. Если чекбокс отмечен, проверяем product.inStock. Это компактная запись для "если фильтр не активен ИЛИ товар в наличии".
filteredProducts - это не состояние, а вычисляемое значение. Оно пересчитывается при каждом рендере на основе products, search, category и inStockOnly. Когда любой из этих параметров меняется, React перерисовывает компонент, и фильтрация выполняется заново. Не нужно синхронизировать отдельное состояние для отфильтрованных товаров.
Компонент App максимально простой - панель фильтров сверху, сетка товаров снизу. Всё состояние и логика находятся здесь, а визуальные компоненты получают только необходимые данные. Filters управляет параметрами фильтрации, ProductGrid показывает результат. Они независимы друг от друга и связаны только через App.