Архитектура Feature-Sliced Design в React

Практический курс по освоению архитектурной методологии FSD для масштабируемых React-приложений. Вы научитесь правильно декомпозировать задачи, управлять зависимостями и создавать поддерживаемую кодовую базу.

1. Введение в Feature-Sliced Design: философия, слои и преимущества архитектуры

Введение в Feature-Sliced Design: философия, слои и преимущества архитектуры

Добро пожаловать в курс по архитектуре Feature-Sliced Design (FSD). Если вы когда-либо работали над React-приложением, которое выросло из небольшого пет-проекта в огромного монстра, где изменение одной кнопки ломает логику на другой странице, то этот курс для вас.

В этой первой статье мы разберем фундамент: что такое FSD, зачем он нужен и как он помогает превратить хаос в порядок.

Проблема масштабируемости во фронтенде

В начале разработки любого проекта структура кажется простой. Обычно мы видим папки components, hooks, utils и pages. Это классический подход, который отлично работает для небольших приложений. Но по мере роста бизнеса и функционала возникают проблемы:

* Неявные связи: Компонент из одной страницы импортирует что-то из другой, создавая запутанный клубок зависимостей. * Сложность навигации: Папка components разрастается до сотен файлов, и найти нужный становится квестом. * Трудности онбординга: Новому разработчику сложно понять, где находится бизнес-логика, а где просто UI.

!Сравнение хаотичной архитектуры (Spaghetti code) и структурированного подхода.

Feature-Sliced Design (FSD) — это архитектурная методология для фронтенд-проектов, призванная решить эти проблемы через строгое разделение ответственности и явные правила взаимодействия модулей.

Философия FSD: Разделяй и властвуй

Главная идея FSD заключается в том, чтобы делить приложение не по техническому признаку (компоненты, хуки, стили), а по бизнес-ценности (фичи, сущности, страницы).

Архитектура строится на трех ключевых концепциях иерархии:

  • Слои (Layers) — верхнеуровневое разделение ответственности.
  • Слайсы (Slices) — модули, разделяющие код по предметной области (например, пользователь, корзина, товар).
  • Сегменты (Segments) — техническое разделение внутри слайса (ui, model, api, lib).
  • Давайте разберем самую важную часть — слои.

    Слои: Анатомия приложения

    В FSD существует строгая иерархия слоев. Они располагаются от самого абстрактного и переиспользуемого (внизу) до самого конкретного и специфичного для бизнеса (вверху).

    !Иерархия слоев в Feature-Sliced Design.

    Стандартный набор слоев выглядит так (снизу вверх):

    1. Shared (Общее)

    Это фундамент вашего приложения. Здесь находится код, который ничего не знает о бизнесе: UI-кит (кнопки, инпуты), вспомогательные функции, конфигурации API.

    > Код в Shared не должен зависеть ни от каких других слоев.

    2. Entities (Сущности)

    Здесь живут бизнес-сущности, с которыми работает ваше приложение. Например: User (пользователь), Product (товар), Order (заказ).

    В этом слое мы описываем, как выглядит сущность (карточка товара) и как она устроена (модель данных), но не описываем сложные взаимодействия.

    3. Features (Фичи)

    Слой пользовательских сценариев. Это действия, которые несут бизнес-ценность.

    Примеры: * AuthByEmail (авторизация по email) * AddToCart (добавление в корзину) * LikePost (лайк поста)

    Фичи используют сущности и shared-компоненты, чтобы реализовать конкретное действие.

    4. Widgets (Виджеты)

    Самостоятельные блоки страницы, объединяющие фичи и сущности. Виджеты — это крупные куски интерфейса.

    Примеры: * Header (шапка сайта) * ProductList (список товаров с фильтрами) * PostCard (карточка поста с комментариями и лайком)

    5. Pages (Страницы)

    Слой страниц приложения. Страница собирается из виджетов, фич и сущностей. В этом слое мы определяем структуру конкретного экрана (например, HomePage, ProfilePage).

    6. App (Приложение)

    Точка входа. Здесь происходит инициализация: подключение провайдеров (Redux, Theme, Router), глобальные стили и настройки.

    Правило зависимостей (The Dependency Rule)

    Это самое важное правило в FSD, которое нельзя нарушать.

    Модуль может импортировать функциональность только из нижележащих слоев.

    Это означает: * Features могут использовать Entities и Shared. * Entities могут использовать только Shared. * Shared не может использовать ничего из вышеперечисленного. * Pages могут использовать всё, кроме App.

    Запрещены: * Импорты сверху вниз (например, Entity не может знать о Feature). * Кросс-импорты внутри одного слоя (одна Feature не должна импортировать другую Feature).

    Если вам нужно, чтобы одна фича использовала другую, это сигнал о том, что либо логику нужно вынести в виджет, либо пересмотреть декомпозицию.

    Внутренняя структура: Слайсы и Сегменты

    Каждый слой состоит из Слайсов (папок по бизнес-домену). Внутри слайса код делится на Сегменты по техническому назначению.

    Пример структуры папок:

    Такая структура позволяет разработчику мгновенно понимать, где искать код. Если сломалась кнопка "Купить" — это features/add-to-cart. Если нужно поправить верстку карточки товара — это entities/product.

    Преимущества FSD

    Почему стоит тратить время на изучение и внедрение этой архитектуры?

  • Явные связи: Благодаря правилу зависимостей, поток данных всегда предсказуем (снизу вверх).
  • Заменяемость и рефакторинг: Вы можете удалить целую фичу или сущность, просто удалив одну папку, и быть уверенным, что не сломали Shared слой.
  • Параллельная разработка: Разные разработчики могут работать над разными слайсами, практически не создавая конфликтов при слиянии веток.
  • Масштабируемость: Архитектура не деградирует при росте проекта. Добавление сотой фичи происходит так же структурно, как и первой.
  • Недостатки и когда НЕ стоит использовать FSD

    Будем честны, серебряной пули не существует. У FSD есть свои минусы:

    * Высокий порог входа: Команде нужно время, чтобы привыкнуть к правилам и перестать думать категориями "умных и глупых" компонентов. * Многословность (Boilerplate): Создание большого количества папок и файлов может показаться избыточным для простых задач.

    > FSD — это инструмент для долгоживущих и развивающихся проектов. Для лендинга, прототипа или MVP на пару недель эта архитектура будет избыточной (Overengineering).

    Заключение

    Feature-Sliced Design предлагает стандартизированный подход к организации кода, который спасает проекты от превращения в хаос. Мы разделяем код по слоям ответственности и бизнес-доменам, соблюдая строгую направленность зависимостей.

    В следующих статьях мы на практике создадим React-приложение, последовательно реализуя каждый слой, начиная с настройки окружения и заканчивая сложной бизнес-логикой.

    Для более глубокого погружения вы можете ознакомиться с официальной документацией Feature-Sliced Design.

    2. Анатомия модуля: слайсы, сегменты и организация кода внутри слоев

    Анатомия модуля: слайсы, сегменты и организация кода внутри слоев

    В предыдущей статье мы рассмотрели глобальную структуру Feature-Sliced Design: слои. Мы узнали, что приложение строится как слоеный пирог, где Shared лежит в основании, а App — на вершине. Но если слои — это полки в нашем шкафу, то что лежит на этих полках? Как организовать код внутри конкретной фичи или сущности, чтобы не превратить папку в свалку файлов?

    Сегодня мы спустимся на уровень ниже и изучим анатомию модуля. Мы разберем понятия Слайс (Slice) и Сегмент (Segment), а также научимся правильно использовать Public API (публичный интерфейс) для защиты нашего кода от запутанных связей.

    Иерархия декомпозиции

    Чтобы понять структуру FSD, нужно запомнить простую иерархию вложенности. Она состоит из трех уровней:

  • Слой (Layer) — определяет масштаб влияния и ответственность (например, features, entities).
  • Слайс (Slice) — определяет предметную область (бизнес-домен). О чем этот код? О пользователе? О корзине? О чате?
  • Сегмент (Segment) — определяет техническое назначение кода. Это UI-компонент? Это стейт-менеджер? Это запрос к API?
  • !Визуализация иерархии: Слой -> Слайс -> Сегмент

    Давайте разберем каждый элемент подробнее.

    Слайсы: Разделение по бизнес-домену

    Слайс — это папка внутри слоя, которая группирует код по конкретной бизнес-сущности или фиче. Название слайса всегда отвечает на вопрос: «С какой частью бизнеса связан этот код?».

    Примеры слайсов

    В слое entities (Сущности) слайсы — это объекты реального мира, с которыми работает приложение: * user (Пользователь) * post (Статья/Пост) * comment (Комментарий) * currency (Валюта)

    В слое features (Фичи) слайсы — это действия или функциональность, несущая ценность: * auth-by-email (Авторизация по email) * send-comment (Отправка комментария) * theme-switcher (Переключение темы)

    Правила именования

    Названия слайсов пишутся в kebab-case (строчными буквами через дефис). Это стандарт для папок в веб-разработке.

    > Важно: Слайсы не могут быть вложены друг в друга. Вы не можете создать папку entities/user/settings. Если вам нужно выделить настройки пользователя, вы либо расширяете слайс user, либо создаете новый слайс user-settings, либо (что чаще всего) это становится отдельной фичей.

    Сегменты: Техническое разделение

    Внутри каждого слайса код делится на стандартизированные папки — сегменты. Это помогает разработчику, открывшему любой слайс, мгновенно понять, где лежит верстка, а где логика.

    Основные сегменты в FSD:

    1. ui (User Interface)

    Здесь хранятся React-компоненты, стили и все, что касается отображения.

    Пример для слайса features/auth-by-email: * ui/LoginForm.tsx * ui/LoginForm.module.css

    2. model (Business Logic)

    Это мозг слайса. Здесь живут стейт-менеджеры (Redux, Zustand, MobX), хуки с бизнес-логикой, селекторы и схемы валидации.

    Пример: * model/slice.ts (Redux slice) * model/selectors.ts * model/types.ts

    3. api (Server Interaction)

    Здесь описываются запросы к бэкенду, связанные с этим слайсом.

    Пример: * api/authApi.ts (RTK Query или axios запросы) * api/dto.ts (Data Transfer Objects — типы данных с сервера)

    4. lib (Library/Utils)

    Вспомогательные функции, которые нужны только внутри этого слайса. Если функция нужна в нескольких слайсах, ее место в слое Shared.

    Пример: * lib/validateEmail.ts * lib/mapUserStatus.ts

    5. config (Configuration)

    Конфигурационные файлы, константы, перечисления (enums), которые относятся к слайсу.

    Пример: * config/constants.ts

    Public API: Инкапсуляция и Index.ts

    Одна из самых мощных концепций FSD — это Public API (Публичный интерфейс).

    Представьте, что слайс — это микросхема. У нее внутри сотни проводов и транзисторов (ваши файлы в model, ui, lib), но наружу торчат только несколько контактов (экспортов), к которым можно подключиться.

    В корне каждого слайса обязан лежать файл index.ts. Это единственная точка входа для внешнего мира.

    Зачем это нужно?

  • Изоляция изменений: Если вы решите переписать внутреннюю логику слайса, переименовать файлы или сменить библиотеку стейт-менеджмента, внешний код об этом не узнает, пока вы сохраняете те же экспорты в index.ts.
  • Контроль доступа: Вы можете скрыть вспомогательные компоненты и функции, которые не предназначены для использования снаружи.
  • Удобство импорта: Вместо длинных путей вы получаете короткие и понятные.
  • Пример реализации Public API

    Допустим, у нас есть сущность entities/user. Структура файлов:

    Содержимое entities/user/index.ts:

    Теперь, когда мы захотим использовать пользователя в фиче, мы напишем:

    !Принцип работы Public API и изоляции модуля

    Особенности слоя Shared

    Слой Shared отличается от остальных. В нем нет слайсов в привычном понимании, так как там нет бизнес-логики.

    В Shared мы сразу делим код на сегменты:

    Внутри shared/ui каждый компонент (например, Button) может иметь свою папку и свой index.ts, но они не привязаны к бизнесу.

    Кросс-импорты: Правила дорожного движения

    Внутри слайса и между ними действуют строгие правила импортов, которые предотвращают спагетти-код.

    1. Внутри одного слайса

    Сегменты могут свободно импортировать друг друга. * ui может использовать model (чтобы достать данные). * model может использовать api (чтобы сделать запрос). * Все могут использовать lib.

    > Совет: Старайтесь избегать циклических зависимостей (когда A импортирует B, а B импортирует A). Обычно поток данных идет: UI -> Model -> API.

    2. Между слайсами одного слоя

    Это строго запрещено.

    * Слайс features/auth НЕ МОЖЕТ импортировать что-то из features/cart. * Слайс entities/product НЕ МОЖЕТ импортировать из entities/user.

    Почему? Это создает сильную связность (Coupling). Если фичи зависят друг от друга, вы не сможете отключить одну, не сломав другую.

    Что делать, если связь нужна?

  • Поднять логику выше — в слой Widgets или Pages, где вы объедините эти две фичи.
  • Вынести общую логику ниже — в Shared (если это утилиты) или в Entities (если это данные).
  • Пример полной структуры модуля

    Давайте соберем всё вместе на примере фичи "Добавление в корзину" (add-to-cart).

    В index.ts мы экспортируем только AddToCartButton. Остальное (модалка, логика, API) скрыто внутри, так как внешнему миру нужно просто вставить кнопку на страницу товара.

    Преимущества такой организации

  • Когнитивная легкость: Открывая папку, вы точно знаете, что ui — это React, а model — это данные. Не нужно гадать.
  • Безопасный рефакторинг: Благодаря Public API вы можете полностью переписать внутренности слайса, не ломая остальное приложение.
  • Масштабируемость: Когда проект вырастает до 1000 файлов, эта структура не дает ему развалиться, так как каждый слайс — это маленькое изолированное приложение.
  • Заключение

    Мы разобрали анатомию модуля в Feature-Sliced Design. Теперь вы знаете, что: * Слайс — это бизнес-единица. * Сегмент — это техническая единица. * Public API — это страж, охраняющий границы модуля.

    В следующей статье мы перейдем от теории к практике и начнем настраивать окружение для нашего React-приложения, чтобы своими руками создать первые слои и слайсы.

    Для закрепления материала рекомендую ознакомиться с разделом "Структура" в документации FSD.

    3. Разработка бизнес-логики: Entities, Features и Widgets на примере React-компонентов

    Разработка бизнес-логики: Entities, Features и Widgets на примере React-компонентов

    Мы уже разобрали теорию слоев и анатомию слайсов. Теперь пришло время самой интересной части — практики. Как именно писать код, чтобы он соответствовал принципам Feature-Sliced Design (FSD)? Как соединить «глупую» карточку товара с «умной» кнопкой добавления в корзину, не нарушая правил зависимостей?

    В этой статье мы построим фрагмент реального приложения: ленту статей с возможностью лайкать их. Мы пройдем путь от создания сущности до сборки полноценного виджета.

    Entities: Фундамент данных

    Начнем со слоя Entities (Сущности). Напоминаю, что задача этого слоя — отображать бизнес-данные, но не привязываться к конкретным пользовательским сценариям.

    Представим, что у нас есть сущность Article (Статья).

    Задача сущности

    Сущность должна уметь отобразить себя: заголовок, превью текста, обложку. Но она не должна знать, что с ней можно делать (лайкать, удалять, редактировать). Почему? Потому что сегодня мы хотим лайкать статьи, а завтра — добавлять в закладки. Если мы зашьем логику лайка внутрь сущности, она станет слишком сложной и зависимой.

    Реализация компонента

    Создадим компонент ArticleCard в слайсе entities/article.

    Обратите внимание на проп actionSlot. Это ключевой паттерн в FSD для соединения слоев. Сущность выделяет место под кнопки, но сама не импортирует их.

    Features: Сценарии взаимодействия

    Теперь перейдем к слою Features (Фичи). Здесь живет интерактивность, которая несет бизнес-ценность. Наша фича — «Оценить статью» (лайкнуть).

    Задача фичи

    Фича должна обработать клик пользователя, отправить запрос на сервер и обновить состояние интерфейса. Фича знает о Сущности (потому что мы лайкаем именно статью), но Сущность не знает о Фиче.

    Реализация компонента

    Создадим слайс features/article-rating.

    Здесь мы видим классическую фичу: она содержит логику (хук useRateArticle, который внутри использует API) и UI-элемент (кнопку).

    !Визуализация паттерна слотов: Feature вставляется в Entity через композицию.

    Проблема соединения (Composition Problem)

    У нас есть ArticleCard (Entity) и ArticleRatingButton (Feature). Как их соединить?

    Новичок в FSD часто пытается сделать так:

    Почему это плохо?

  • Циклическая зависимость: Фича часто зависит от сущности (ей нужен ID статьи). Если сущность начнет зависеть от фичи, круг замкнется.
  • Жесткая связность: Вы не сможете использовать карточку статьи в админке, где лайки не нужны, или в корзине, где нужна кнопка «Удалить».
  • Решение — композиция на верхнем уровне. Для этого существуют слои Widgets и Pages.

    Widgets: Сборка конструктора

    Слой Widgets (Виджеты) нужен для того, чтобы объединять сущности и фичи в самостоятельные блоки. Виджеты самодостаточны: они сами запрашивают данные и сами определяют, какие фичи доступны пользователю.

    Создадим виджет widgets/articles-feed (Лента статей).

    Реализация виджета

    Разбор полетов

    Посмотрите, что произошло в виджете:

  • Виджет импортировал ArticleCard из слоя Entities.
  • Виджет импортировал ArticleRatingButton из слоя Features.
  • Виджет соединил их, передав кнопку как проп actionSlot в карточку.
  • Правила зависимостей соблюдены: * Widget -> Entity (можно) * Widget -> Feature (можно) * Entity ничего не знает о Feature.

    Вариативность использования

    Представьте, что теперь нам нужно создать страницу «Админка статей», где вместо лайков должна быть кнопка «Редактировать».

    Благодаря нашему подходу, нам не нужно переписывать ArticleCard. Мы просто создадим новый виджет AdminArticleList:

    Это и есть сила слабой связности (Low Coupling).

    Работа с данными в слоях

    Частый вопрос: «Где хранить данные? В Entity, Feature или Widget?»

    1. Данные Сущности (Entity Data)

    Данные, описывающие сам объект (название, id, контент), хранятся в слое Entities. Обычно это Redux-слайс или стор entities/article/model/slice.ts.

    2. Данные Фичи (Feature Data)

    Данные, специфичные для действия (статус загрузки лайка, текст комментария в инпуте), хранятся внутри фичи. Глобальному стору не обязательно знать, что пользователь прямо сейчас печатает в поле ввода комментария, пока он не нажал «Отправить».

    3. Данные Виджета (Widget Data)

    Виджеты редко имеют свои данные, но они часто отвечают за загрузку списков. Виджет ArticlesFeed может инициировать запрос за статьями, но результат этого запроса (список статей) лучше сохранить в entities/article, чтобы другие части приложения могли переиспользовать эти данные (например, кэш).

    Чек-лист проверки архитектуры

    Когда вы разрабатываете компонент, задайте себе вопросы:

  • Может ли этот компонент существовать без бизнес-логики?
  • * Да -> Shared/UI * Нет -> Идем дальше.
  • Отражает ли компонент объект реального мира (пользователь, товар)?
  • * Да -> Entities
  • Является ли компонент законченным действием (авторизация, покупка)?
  • * Да -> Features
  • Собирает ли компонент страницу из блоков?
  • * Да -> Widgets

    Заключение

    Мы рассмотрели практическую реализацию трех основных слоев бизнес-логики в React.

    * Entities — это «тело» вашего приложения. Они глупые, красивые и переиспользуемые. * Features — это «душа» и действия. Они оживляют сущности. * Widgets — это «сцена», где тело и душа встречаются.

    Используя паттерн render props или slots (передача компонентов через пропсы), вы можете элегантно обходить ограничения на импорты и создавать гибкие, поддерживаемые интерфейсы.

    В следующей статье мы поднимемся на самый верх и поговорим о слоях Pages и App, а также о том, как настроить роутинг в FSD-приложении.

    4. Сборка приложения: слой App, настройка роутинга и композиция страниц

    Сборка приложения: слой App, настройка роутинга и композиция страниц

    Мы прошли долгий путь. Мы научились создавать независимые UI-компоненты в Shared, моделировать бизнес-данные в Entities, реализовывать пользовательские сценарии в Features и объединять их в самостоятельные блоки в Widgets. У нас есть все детали конструктора, но пока нет самого здания.

    В этой завершающей статье теоретического блока мы разберем два верхних слоя архитектуры Feature-Sliced Design (FSD): Pages (Страницы) и App (Приложение). Мы узнаем, как собрать разрозненные куски кода в единое целое, настроить роутинг и инициализировать приложение.

    Layer Pages: Контекст пользователя

    Слой Pages — это место, где собирается композиция для конкретного маршрута (URL). Если Widgets — это крупные блоки (например, лента новостей), то Pages — это холст, на котором эти блоки располагаются.

    Ответственность слоя

    Страница в FSD должна быть максимально «тонкой». Она не должна содержать сложной бизнес-логики или стилей, не относящихся к позиционированию блоков.

    Основные задачи страницы:

  • Композиция: Объединение виджетов, фич и сущностей в единый экран.
  • Чтение параметров URL: Получение id из адресной строки (например, /article/42).
  • SEO и Meta-теги: Установка заголовка вкладки браузера и мета-описания.
  • Layout (Макет): Определение того, какой шаблон используется (с сайдбаром, на всю ширину и т.д.).
  • Структура слайсов

    В слое pages слайсы соответствуют маршрутам приложения. Названия слайсов обычно отражают суть страницы.

    Пример структуры:

    Внутри слайса страницы структура стандартная: ui, model (если нужно хранить состояние, специфичное только для этой страницы, например, открытие локальной модалки), lib.

    Пример кода страницы

    Давайте соберем страницу чтения статьи, используя созданные ранее виджеты.

    Обратите внимание: страница просто расставляет компоненты друг за другом. Она делегирует всю работу нижележащим слоям.

    Layer App: Точка входа

    Слой App — это вершина пирамиды FSD. Это единственный слой, который знает обо всем приложении целиком. Здесь происходит инициализация и настройка глобального окружения.

    !Слой App как связующее звено всей архитектуры.

    Что находится в App?

  • Providers (Провайдеры): HOC (Higher Order Components), которые оборачивают приложение (Redux Store, Router, ThemeProvider, ErrorBoundary).
  • Styles (Стили): Глобальные стили, сброс CSS (reset.css), подключение шрифтов.
  • Router (Роутер): Конфигурация маршрутов.
  • Entry Point: Файл index.tsx или main.tsx.
  • Структура папки App

    Настройка Роутинга

    Роутинг в FSD — это место встречи слоя App и слоя Pages.

    > Важно помнить правило зависимостей: Pages не могут импортировать App. Наоборот, App импортирует Pages.

    Конфигурация маршрутов

    Обычно конфигурацию выносят в src/app/providers/router. Здесь мы сопоставляем пути (URL) и компоненты страниц.

    Lazy Loading (Ленивая загрузка)

    Чтобы приложение не грузило код всех страниц сразу, хорошей практикой является использование React.lazy. В FSD это делается внутри Public API страницы.

    В файле src/pages/home/index.ts:

    Теперь, когда App импортирует HomePage, он получает ленивую версию компонента.

    Паттерн Chain of Providers (Цепочка провайдеров)

    В корневом файле часто возникает «ад провайдеров» (Provider Hell):

    В FSD принято выносить эту логику в отдельный HOC withProviders внутри src/app/providers.

    И тогда ваш App.tsx становится чистым:

    Layouts: Где хранить макеты?

    Часто возникает вопрос: где хранить общие макеты (Layouts), например, MainLayout (с шапкой и подвалом) или AuthLayout (центровка формы входа)?

    В FSD есть два подхода:

  • В Shared/UI: Если Layout — это просто «глупая» обертка с CSS-сеткой, то ей место в shared/ui/Layouts.
  • В Widgets: Если Layout содержит бизнес-логику (например, проверяет авторизацию или подгружает настройки пользователя), то это уже ближе к виджету или даже сущности приложения.
  • Чаще всего Layout используется прямо внутри App.tsx (как в примере выше) или внутри конкретных страниц, оборачивая их контент.

    Итоговая картина зависимостей

    Давайте взглянем на полную картину того, как слои взаимодействуют друг с другом в готовом приложении.

    !Поток зависимостей в Feature-Sliced Design.

  • Shared предоставляет UI-кит и утилиты.
  • Entities используют Shared для создания карточек сущностей.
  • Features используют Entities и Shared для создания интерактивных кнопок и форм.
  • Widgets объединяют Features и Entities в законченные блоки.
  • Pages расставляют Widgets по экрану.
  • App подключает Pages к роутеру и оборачивает всё в провайдеры.
  • Заключение

    Поздравляю! Мы полностью разобрали структуру Feature-Sliced Design. Теперь вы знаете, как разложить любой проект по полочкам, от маленькой кнопки до глобального роутера.

    Слой App и Pages завершают нашу конструкцию, превращая набор модулей в работающее приложение. Главное правило, которое нужно помнить на этом этапе: верхние слои служат для композиции нижних.

    В следующих материалах мы перейдем к продвинутым темам: как работать с асинхронностью, тестировать слои и мигрировать существующие проекты на FSD.

    5. Контроль качества: Public API, правила импортов и использование eslint-plugin-fsd

    Контроль качества: Public API, правила импортов и использование eslint-plugin-fsd

    Мы прошли большой путь, изучая слои, слайсы и сегменты. Теперь вы знаете, как разложить код по полочкам. Но давайте будем честны: в реальной разработке, когда горят дедлайны, а команда растет, очень легко нарушить архитектурные границы. Кто-то случайно импортирует фичу в сущность, кто-то «протянет руку» внутрь чужого слайса мимо Public API, а кто-то создаст циклическую зависимость.

    Архитектура хороша ровно настолько, насколько строго она соблюдается. В этой статье мы поговорим о том, как превратить устные договоренности в жесткие правила, контролируемые автоматически. Мы разберем концепцию Public API, правила импортов и настроим eslint-plugin-fsd — стража вашего архитектурного порядка.

    Public API: Единственная дверь в модуль

    В предыдущих статьях мы уже упоминали файл index.ts в корне слайса. Пришло время разобрать его роль детально.

    Public API (Публичный интерфейс) — это контракт, который модуль заключает с остальным приложением. Это список того, что модуль «разрешает» использовать внешнему миру.

    Принцип айсберга

    Представьте слайс как айсберг.

    * Над водой (Public API): То, что видно всем. Обычно это 2-3 компонента и пара селекторов. * Под водой (Internal Implementation): Огромная масса кода: стили, хелперы, внутренние хуки, типы ответов API, мапперы данных.

    !Метафора Public API как айсберга: внешний мир видит только то, что экспортировано через index.ts.

    Почему это важно?

  • Защита от изменений (Refactoring Safety): Если внешний код импортирует компоненты напрямую из папок (import ... from 'entities/user/ui/UserCard'), то любое перемещение файла внутри слайса user сломает весь проект. Если же импорт идет через Public API (import ... from 'entities/user'), вы можете перетряхивать внутренности слайса как угодно, главное — сохранить экспорт в index.ts.
  • Снижение когнитивной нагрузки: Разработчику, который хочет использовать ваш модуль, не нужно копаться в папках ui или model. Он просто пишет import { ... } from '...' и IDE подсказывает ему доступные методы.
  • Инкапсуляция: Вы можете скрыть вспомогательные функции, которые не предназначены для внешнего использования. Это предотвращает неправильное использование вашего кода.
  • Пример правильного Public API

    Файл src/entities/article/index.ts:

    Правила импортов: Законы гравитации FSD

    Чтобы приложение не превратилось в запутанный клубок (Spaghetti Code), в Feature-Sliced Design действуют строгие правила импортов. Их можно сравнить с законами физики: если вы их нарушаете, конструкция падает.

    1. Правило «Только снизу вверх»

    Модуль может импортировать только модули из нижележащих слоев.

    * App -> Pages, Widgets, Features, Entities, Shared * Pages -> Widgets, Features, Entities, Shared * Widgets -> Features, Entities, Shared * Features -> Entities, Shared * Entities -> Shared * Shared -> Никого (только внешние библиотеки)

    2. Запрет кросс-импортов (Cross-imports)

    Модуль не может импортировать другой модуль с того же слоя.

    * ~~features/auth импортирует features/cart~~ — ЗАПРЕЩЕНО. * ~~entities/user импортирует entities/product~~ — ЗАПРЕЩЕНО.

    Исключение: В слое Shared кросс-импорты допустимы, так как там нет бизнес-логики, но лучше и там держать модули независимыми.

    3. Запрет импортов «через голову»

    Нельзя импортировать внутренности модуля, минуя его Public API.

    * ~~import { Button } from 'shared/ui/Button/Button'~~ — Плохо (привязка к структуре файлов). * import { Button } from 'shared/ui/Button'Хорошо (если там есть index.ts).

    Автоматизация: eslint-plugin-fsd

    Человеческий фактор — главная причина архитектурных проблем. Вы можете написать идеальную документацию, но новый разработчик (или вы сами через месяц) забудет проверить уровень слоя и сделает неправильный импорт.

    Для решения этой проблемы сообщество создало плагины для ESLint. Самый популярный подход — использование набора правил, специфичных для FSD.

    Рассмотрим три главных правила, которые должен проверять линтер.

    Правило 1: path-checker (Проверка путей внутри слайса)

    Внутри одного слайса все импорты должны быть относительными.

    Почему? Слайс должен быть самодостаточным. Если вы захотите перенести слайс features/auth в другой проект или просто переименовать папку features в modules (гипотетически), абсолютные импорты сломаются.

    Пример внутри файла src/entities/user/ui/UserCard/UserCard.tsx:

    Линтер с правилом path-checker автоматически подсветит ошибку и предложит автофикс (заменит абсолютный путь на относительный).

    Правило 2: public-api-imports (Импорты только через Public API)

    Это правило запрещает залезать внутрь чужого модуля. Если вы находитесь в features/auth и хотите использовать entities/user, вы обязаны делать это через index.ts.

    Пример в файле src/features/auth/ui/LoginForm.tsx:

    !Визуализация работы правила public-api-imports: запрет прямых путей к файлам модуля.

    Исключение для тестов: В тестовых файлах (.test.ts, .stories.tsx) иногда нужно получить доступ к внутренностям (например, к редюсеру для настройки теста). Правило public-api-imports часто настраивают так, чтобы разрешать импорты из testing.ts (специальный Public API для тестов).

    Правило 3: layer-imports (Проверка слоев)

    Самое важное правило, охраняющее архитектуру от циклических зависимостей и высокой связности. Оно проверяет, что вы не импортируете верхний слой в нижний.

    Пример в файле src/entities/user/model/slice.ts:

    Настройка ESLint

    Для внедрения этих проверок обычно используют готовые решения. Одним из популярных плагинов является @feature-sliced/eslint-config или отдельные плагины вроде eslint-plugin-fsd-stable (названия пакетов могут меняться, важно искать актуальные).

    Пример базовой конфигурации .eslintrc.json при использовании кастомного плагина:

    > Примечание: В конфигурации часто используется alias (псевдоним пути), например @/entities/user. Линтер должен знать о вашем алиасе, чтобы корректно определять слои.

    Что делать с нарушениями?

    Когда вы подключите линтер к существующему проекту, скорее всего, вы увидите сотни ошибок. Это нормально. Не пытайтесь исправить всё за один день.

  • Для новых файлов: Правила должны работать жестко (error).
  • Для старых файлов: Можно временно использовать warn или добавить файлы в .eslintignore, постепенно проводя рефакторинг.
  • Заключение

    Архитектура Feature-Sliced Design мощна, но требовательна. Без автоматического контроля качества она быстро деградирует.

    Внедрение Public API и строгих правил линтинга дает вам:

  • Спокойствие: Вы знаете, что никто случайно не создал циклическую зависимость.
  • Скорость: Новички быстрее понимают, откуда можно импортировать, а откуда нельзя — IDE сама подскажет ошибку.
  • Гибкость: Благодаря инкапсуляции вы можете смело рефакторить внутренности модулей.
  • Теперь, когда мы защитили наш код правилами, мы готовы двигаться дальше. В следующей статье мы поговорим о тестировании в рамках FSD: как тестировать каждый слой изолированно и зачем нам нужны тестовые Public API.