ИИ

FACC-подход в React для гибкого вариативного дизайна карточек

Краткое резюме

В статье рассматривается паттерн FACC (Function as Child Component) для гибкого вариативного дизайна в React. Он позволяет разработчикам свободно компоновать блоки, предоставляя набор готовых элементов и упрощая создание множества вариаций компонентов.

Здравствуйте, это снова Костя из Cloud.ru. Ранее мы обсуждали паттерны as для безопасного полиморфизма и asChild для чистой композиции. Сегодня мы рассмотрим подход, который обеспечивает высокую гибкость в вариативном дизайне — FACC (Function as Child Component). **Проблема: множество вариантов карточек продукта** Представьте, что у вас есть карточка товара, но дизайнеры создали 15 её вариантов: вертикальную с рейтингом, горизонтальную со скидкой, компактную для списков, с видеопревью, с быстрым добавлением в корзину и другие. **Классический подход: использование пропсов** Обычно в таких случаях используются пропсы для различных сценариев: ``` ``` Однако проблема не только в количестве пропсов, но и в сложности понимания того, что происходит внутри компонента при использовании подобных конструкций. Чем больше комбинаций пропсов, тем труднее читать и тестировать компонент. Любое новое условие может вызвать неожиданный побочный эффект и усложнить поддержку. **FACC-решение: дизайн как API** FACC предлагает альтернативу — предоставить разработчику свободу в компоновке, сохранив контролируемую логику: ``` {({ Title, Image, Price, Rating, AddToCart, Container }) => ( <Rating showCount={true} /> <Price fontSize="xl" /> <AddToCart variant="primary" /> </Container> )} </ProductCardFacc> ``` В этом коде разработчик компонента Card не указывает, как расположить заголовок или рейтинг, а просто предоставляет набор готовых блоков, оставляя вёрстку этих элементов на усмотрение разработчика. Это позволяет создать как минимум 120 (5!) вариаций карточки, не считая неограниченного набора кастомных компонентов. **Ограничения FACC** Хотя FACC хорошо подходит для ситуаций, где нужна свобода компоновки, при создании слишком сложных или вложенных FACC-композиций можно столкнуться с той же проблемой, что и при использовании множества пропсов — сложным для чтения и поддержки кодом. Поэтому вложенные или слишком комбинированные FACC следует использовать с осторожностью и только там, где действительно необходима максимальная гибкость интерфейса. </div> <!-- Красивый футер статьи --> <div class="article-footer" style="margin-top: 3rem; padding: 1.5rem; background: linear-gradient(135deg, rgba(16, 185, 129, 0.03), rgba(5, 150, 105, 0.01)); border-radius: 0.75rem; border: 1px solid rgba(16, 185, 129, 0.1);"> <!-- Первая строка: Дата, Категория, Просмотры, Лайки --> <div style="display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: center; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 1px solid rgba(16, 185, 129, 0.1);"> <div style="display: flex; align-items: center; gap: 0.5rem; color: var(--gray); font-size: 0.95rem;"> <i class="fas fa-calendar-alt" style="color: var(--primary);"></i> <time datetime="2025-11-25T11:36:03" itemprop="datePublished"> 25.11.2025 11:36 </time> </div> <div style="display: flex; align-items: center; gap: 0.5rem; color: var(--gray); font-size: 0.95rem;"> <i class="fas fa-folder" style="color: var(--primary);"></i> <a href="/category/ii" style="color: var(--primary); text-decoration: none; font-weight: 500;">ИИ</a> </div> <div style="display: flex; align-items: center; gap: 0.5rem; color: var(--gray); font-size: 0.95rem;"> <i class="fa-solid fa-eye" style="color: var(--primary);"></i> <span>3</span> </div> <div class="like-button" data-id="50119" style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer; color: var(--gray); font-size: 0.95rem; transition: all 0.2s;"> <i class="fa-regular fa-heart" style="color: var(--primary);"></i> <span class="like-count">0</span> </div> </div> <!-- Вторая строка: Источник и первоисточник --> <div style="display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; padding-bottom: 1rem; border-bottom: 1px solid rgba(16, 185, 129, 0.1);"> <i class="fas fa-newspaper" style="color: var(--primary); font-size: 0.95rem;"></i> <span style="color: var(--gray); font-size: 0.9rem;">Источник:</span> <a href="https://habr.com/ru/companies/cloud_ru/articles/962824/?utm_source=habrahabr&utm_medium=rss&utm_campaign=962824" target="_blank" rel="noopener nofollow" style="color: var(--primary); text-decoration: none; font-weight: 500; font-size: 0.95rem;"> Все публикации подряд на Хабре <i class="fas fa-external-link-alt" style="font-size: 0.75rem; margin-left: 0.25rem;"></i> </a> </div> <!-- Третья строка: Теги --> <div class="tag-list" style="display: flex; flex-wrap: wrap; gap: 0.75rem;"> <a href="/?tag=Facc" class="tag">#Facc</a> <a href="/?tag=React.js" class="tag">#React.js</a> <a href="/?tag=Typescript" class="tag">#Typescript</a> <a href="/?tag=%D0%9F%D0%BE%D0%BB%D0%B8%D0%BC%D0%BE%D1%80%D1%84%D0%B8%D0%B7%D0%BC" class="tag">#Полиморфизм</a> <a href="/?tag=%D0%9A%D0%BE%D0%BC%D0%BF%D0%BE%D0%B7%D0%B8%D1%86%D0%B8%D1%8F" class="tag">#Композиция</a> </div> </div> <div class="back-link"> <a href="/" class="nav-link"> <i class="fas fa-arrow-left"></i> Вернуться к ленте </a> </div> </article> <!-- Похожие новости --> </main> <footer class="footer" role="contentinfo"> <div class="footer-container"> <!-- Основные разделы сайта --> <div class="footer-sections"> <div class="footer-column"> <h3 class="footer-heading">Навигация</h3> <nav aria-label="Основная навигация"> <ul class="footer-nav-list"> <li><a href="/">Главная страница</a></li> <li><a href="/categories">Все категории</a></li> <li><a href="/?sort=popular">Популярные новости</a></li> <li><a href="/?sort=new">Последние новости</a></li> <li><a href="/rss.xml" rel="nofollow">RSS лента</a></li> </ul> </nav> </div> <div class="footer-column"> <h3 class="footer-heading">О проекте</h3> <nav aria-label="О проекте"> <ul class="footer-nav-list"> <li><a href="https://t.me/toplentaru" target="_blank" rel="noopener">Telegram канал</a></li> <li><a href="mailto:support@toplenta.ru">Контакты</a></li> </ul> </nav> <p style="margin-top: 1rem; font-size: 0.85rem; color: var(--gray); line-height: 1.5;"> Toplenta — агрегатор актуальных новостей России и мира. Следите за важными событиями в политике, экономике, технологиях и других сферах. </p> </div> </div> <div class="footer-note"> <p> Все материалы взяты из открытых источников (RSS-лент), права принадлежат их авторам. Ссылки на первоисточники указаны в каждой публикации. </p> <p> Если вы являетесь правообладателем — <a href="mailto:support@toplenta.ru">свяжитесь с нами</a> для оперативного реагирования. </p> </div> </div> <div class="footer-bottom"> <p>© Toplenta — все права принадлежат соответствующим авторам.</p> </div> </footer> </div> <!-- Модальное окно фильтров (вне container чтобы избежать конфликтов стилей) --> <!-- Floating кнопка фильтров (только на мобильном) --> <button class="filters-floating-btn" id="filters-btn" aria-label="Открыть фильтры"> <i class="fas fa-sliders-h"></i> </button> <div class="filters-modal-overlay" id="filters-overlay"></div> <div class="filters-modal" id="filters-modal"> <div class="filters-modal-header"> <h2 class="filters-modal-title">Фильтры и сортировка</h2> <button class="filters-modal-close" id="filters-close" aria-label="Закрыть"> <i class="fas fa-times"></i> </button> </div> <div class="filters-modal-body"> <!-- Фильтры и сортировка для модалки --> <div class="filters-container"> <div class="filters-wrapper"> <!-- Категория --> <div class="filter-group"> <label class="filter-label"> <i class="fas fa-folder"></i> Категория: </label> <select class="filter-select filter-category" id="modal-category-select" name="category"> <option value="" selected> Все категории </option> </select> </div> <!-- Сортировка --> <div class="filter-group"> <label class="filter-label"> <i class="fas fa-sort"></i> Сортировка: </label> <select class="filter-select filter-sort" id="modal-sort-select" name="sort"> <option value="newest" selected> Сначала новые </option> <option value="oldest" > Сначала старые </option> <option value="popular" > Популярные </option> <option value="most_liked" > Больше лайков </option> </select> </div> <!-- Период --> <div class="filter-group"> <label class="filter-label"> <i class="fas fa-calendar"></i> Период: </label> <select class="filter-select filter-period" id="modal-period-select" name="period"> <option value="all" selected> Все время </option> <option value="today" > Сегодня </option> <option value="week" > Неделя </option> <option value="month" > Месяц </option> </select> </div> <!-- Кнопка сброса --> </div> </div> </div> </div> <div id="toast" class="toast hidden"></div> <script src="/static/js/main.min.js?v=9d9623e" defer></script> <!-- Yandex.Metrika counter (async) --> <script type="text/javascript" async> (function(m,e,t,r,i,k,a){ m[i]=m[i]||function(){ (m[i].a=m[i].a||[]).push(arguments) }; m[i].l=1*new Date(); k=e.createElement(t), a=e.getElementsByTagName(t)[0]; k.async=1; k.src=r; a.parentNode.insertBefore(k,a) })(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); ym(100805785, "init", { clickmap:true, trackLinks:true, accurateTrackBounce:true, webvisor:true }); </script> <noscript><div><img src="https://mc.yandex.ru/watch/100805785" style="position:absolute; left:-9999px;" alt="" /></div></noscript> <!-- /Yandex.Metrika counter --> </body> </html>