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

Здесь я, в основном, описываю структурирование по рекомендациям методологии. А в следующей статье, напротив, рассмотрю, как можно улучшить структуру проекта, намеренно нарушая рекомендации FSD. Заранее предупрежу, что правила методологии носят рекомендательный, а не обязательный характер. Их назначение — задать направление структурирования, а дальше принимать решения нужно в зависимости от конкретного проекта и ситуации в нём. Строгое же следование рекомендациям может привести к бо̒льшим проблем, чем их нарушение.

Если заметите ошибки — пишите в комментариях!

Содержание

  1. Слой shared

    1.1 Как избежать свалки в shared/lib?

    1.2 Направление зависимостей в слое shared

    1.3 Может ли store (слайс Redux store в случае Redux Toolkit) находиться в слое shared?

    1.4 В слое shared стоит размещать только то, что можно переиспользовать в других проектах?

  2. Сегменты

    2.1 Ошибки группирования файлов в сегментах

    2.2 Может ли логика интерфейса, константы, хуки и типы находиться в сегменте ui?

    2.3 Когда для сегмента создавать папку, а когда файл?

    2.4 Можно ли создавать в сегментах публичный API (файлы index.ts)?

  3. Слайсы и связь между ними

    3.1 Может ли слайс в entities содержать несколько бизнес-сущностей?

    3.2 Стоит ли создавать для каждой сущности слайс с одним и тем же именем в каждом из слоёв: entities, features, widgets, pages?

    3.3 Всегда ли нужно создавать публичный API (файлы index.ts в слайсах)?

    3.4 Могут ли вложенные слайсы и сегменты быть на одном уровне?

    3.5 Что делать, когда сущности связаны?

    3.6 @x-нотация для публичного API

  4. Слои

    4.1 Стоит ли вводить дополнительные промежуточные слои, которых нет в FSD, чтобы избежать кросс-импортов?

    4.2 Где разместить функциональность запросов к эндпоинтам и DTO-типы?

    4.3 Где размещать мапперы в DTO и во View, переводы, схемы валидации, layout-ы страниц?

1. Слой shared

1.1. Как избежать свалки в shared/lib?

Иногда в shared/lib создают папки вроде hooks, consts, types и т. д., что является неправильным подходом. В документации описано, что это ведёт к группированию файлов по их типу, а не по решаемым ими задачам, а это противоречит принципам FSD. Это создаёт сложности, поскольку каждый файл предназначен для решения несвязанных задач, и их, скорее всего, не получится объединить в подсистему.

Более правильным группированием будет создание в shared/lib папок, таких как:

? validation

? modals

? notifications

? localization

? urlUtils

Например, shared/lib/notifications содержит утилиты, хуки, компоненты интерфейса, константы и типы, реализующие механизм уведомлений, используемый в различных частях приложения. Может показаться, что компоненты этой подсистемы следует располагать отдельно в shared/ui, но в таком случае сильно связанные файлы будет разделены. Уточню, что здесь речь не о том, в какой папке размещать механизм уведомлений, он взят в качестве примера. Речь о том, что файлы подобных подсистем зачастую лучше держать рядом.

Может получиться так, что в shared/lib скопится много инфраструктурных подсистем и различных наборов утилит, дополняющих API JavaScript, браузера и сторонних библиотек, и хранить их все в одной папке станет неудобно. FSD не даёт ответа, что делать в таком случае.

1.2. Направление зависимостей в слое shared

Хаотичные связи в этом слое, как и в других слоях, приведут к запутанной структуре. Поэтому желательно контролировать связи между сегментами и их направление. Например, чтобы сегмент ui импортировал файлы из сегмента lib, но не наоборот.

1.3. Может ли store (слайс Redux store в случае Redux Toolkit) находиться в слое shared?

Да, может.

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

1.4. В слое shared стоит размещать только то, что можно переиспользовать в других проектах?

Если не ошибаюсь, то в прошлом в методологии так и рекомендовалось. Сейчас же такого ограничения нет: в shared разрешается размещать API, маршрутизацию, авторизацию, переводы, темизацию и прочую инфраструктурную функциональность. Перечисленная функциональность (за исключением API) не относится к специфике приложения/бизнеса. Она нужна для работы приложения и для удобства работы с приложением, но не для решения каких-либо задач пользователя или бизнеса.

2. Сегменты

2.1. Ошибки группирования файлов в сегментах

Неправильно создавать сегменты вроде hooks, types, consts. Об этом написано в документации. В предыдущей статья я уже писал, что назначение стандартных сегментов в бизнес-слоях, за исключением сегментов lib и config, такое же, как и у паттернов MVC, MVP, MVVM, Flux и т. д.: группирование кода приложения по техническому назначению.  В отличие от подобных паттернов, в FSD предлагается группировать такой код ещё и на уровне файловой структуры проекта.

2.2. Может ли логика интерфейса, константы, хуки, типы находиться в сегменте ui?

Может. Неправильно ограничивать сегмент ui только компонентами.

В бизнес-слоях FSD он является аналогом слоя представления в таких паттернах, как MVC, MVP, MVVM, Flux и т. д. В нём могут содержаться хуки, типы (обычно это props, но могут быть типы для других функций и объектов), константы, различная логика интерфейса и утилиты, относящиеся именно к сегменту ui и используемые только в нём. Если же вынести эти файлы в другие сегменты, то получится, что сильно связанный код станет разделён, а это противоречит принципам Low Coupling и High Cohesion.

2.3. Когда для сегмента создавать папку, а когда файл?

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

Рекомендация создавать папку-сегмент, если в сегменте более одного файла, может быть неудобным в случае слайсов с небольшим количеством файлов. А кто-то предпочитает сразу делать сегменты папками, аргументируя тем, что потом слайс может разрастись и придётся переносить файлы сегментов в папки. Но если это заранее неизвестно, то зачем усложнять без необходимости? О том, что не нужно усложнять структуру папок без необходимости, довольно хорошо написано в документации к другой методологии.

2.4. Можно ли создавать публичный API (файлы index.ts) в сегментах?

Это допустимо, но в подавляющем большинстве случаев избыточно. Файлы index.ts предназначены для предоставления доступа только к публичным файлам слайса.

Но думаю, что в редких ситуациях с большим количеством файлов в сегментах слайса всё-таки может быть польза от создания файлов index.ts для экспорта в другие сегменты этого же слайса. В таком случае использование публичного API может помочь избежать путаницы в связях между файлами разных сегментов и ограничить импорт приватных файлов в соседние сегменты.

3. Слайсы и связь между ними

3.1. Может ли слайс в entities содержать несколько бизнес-сущностей?

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

3.2. Стоит ли создавать для каждой сущности слайс с одним и тем же именем в каждом из слоёв: entities, features, widgets, pages?

Если у вас группы слайсов, то, возможно, сто̒ит, но обычно — нет.  Если для каждой сущности (например, product) в каждом или почти в каждом слое создаётся папка с именем этой сущности, например:

entities/products/

features/products/

widgets/products/

pages/products/

то, возможно, вы неправильно поняли, по какому принципу в FSD создаются слайсы.

Только слайс из слоя entities привязан к одной или нескольким связанным бизнес-сущностям. А слайсы в слоях features, widgets и pages обычно не привязаны к определённой бизнес-сущности. Какая-нибудь фича может быть общей для группы сущностей или вообще для всех. Какой-нибудь виджет может работать одновременно с несколькими сущностями. А, например, на странице Dashboard может использоваться десяток сущностей.

Зачастую стоит выделять слайсы, относящиеся к определённой функциональности, а не сущности. Например, слайс в слое features, реализующий механизм фильтрации и импортирующий файлы из нескольких слайсов слоя entities.

Если же вы не переиспользуете фичи и виджеты определённой сущности в нескольких слайсах на уровнях выше, и не планируете это делать, то обычно лучше поместить их в папку pages, как это рекомендуется в подходе pages-first.

3.3. Всегда ли нужно создавать публичный API (файлы index.ts в слайсах)?

Нет, это зависит от особенностей проекта, требований к разделению на чанки и наличия проблем с циклическими зависимостями.

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

Не стоит использовать импортирование из публичного API внутри самого слайса, поскольку это может привести к возникновению циклических зависимостей.

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

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

3.5. Что делать, когда сущности связаны?

Возможны разные ситуации и варианты решения:

  • Их можно рассматривать как один слайс (без вложенных слайсов) и не делить на отдельные слайсы. То есть во всех слоях считать их одной сущностью, состоящей из нескольких.

  • Несколько связанных слайсов можно сгруппировать в один большой слайс с вложенными слайсами.

  • Использовать кросс-импорты в рамках одного слоя с использованием публичного API и @x-нотации.

  • Варианты с нарушением FSD в зависимости от ситуации, но об этом в следующей статье.

3.6. @x-нотация для публичного API

В FSD с недавнего времени стало допустимо использовать «правильные кросс-импорты» через дополнительные публичные API. Для этого предлагается использовать @x-нотацию и только в слое Entities. Причины её появления описаны тут.

Кому-то такая нотация может показаться неудобной. Это лишь одна из рекомендаций методологии, а не призыв к действию. Можно отступить от неё и использовать другой подход.

4. Слои

4.1. Стоит ли вводить дополнительные промежуточные слои, которых нет в FSD, чтобы избежать кросс-импортов?

Не рекомендуется вводить дополнительные слои. В FSD и без того довольно много слоёв, и даже стандартных рекомендуется использовать как можно меньше. Рассмотрите альтернативы: объединение связанных слайсов, передача через контекст или props, добавление нескольких кросс-импортов, и т. д.

4.2. Где разместить функциональность запросов к эндпоинтам и DTO-типы?

Ответ зависит от конкретного проекта. В одних случаях оптимальным вариантом будет размещение в слое shared, в других — в бизнес-слоях. При этом инфраструктурный код API размещают в shared/api.

Насколько я понял, в FSD обычно принято использовать следующие варианты:

  • в shared/api/ — если API генерируется или если в нём много связей между разными бизнес-сущностями;

  • в entities/{имя_слайса}/api/ — при использовании FSD ниже версии 2.1 для проектов, где редки связи между API разных сущностей;

  • В сегменте api в том же бизнес-слайсе, где вызывается API-запрос когда проект ориентирован на pages-first из FSD 2.1 и редки связи между API разных сущностей.

В документации подробно описаны:

4.3. Где размещать мапперы в DTO и во View, переводы, схемы валидации, layout-ы страниц?

Многое уже описано в документации по типам. Про расположение схем валидации написано в пункте «Схемы валидации типов и Zod». Про расположение layout-ов в разных ситуациях написано здесь.

Мапперы из DTO вo View

Официальная рекомендация: размещать рядом с DTO. Я с ней не согласен. Для одного DTO может потребоваться несколько мапперов для разных элементов интерфейса, страниц. Если же в проекте весь API находится в слое shared, то в нём окажутся ещё и типы бизнес-блоков интерфейса. В будущем при неаккуратном удалении ненужных компонентов интерфейса они могут там и остаться. На мой взгляд, мапперы правильнее размещать в слайсе, где находится компонент страницы, для отображения в котором делается преобразование. 

Насчёт того, в каком сегменте располагать, я считаю, из стандартных сегментов наиболее подходит сегмент model.

Мапперы из View в DTO

Видел, что в официальном чате обсуждался вариант располагать ближе к формам — в сегменте ui, а также обсуждался вариант располагать в сегменте model. На мой взгляд, второй вариант предпочтительнее, да и в таком случае обе разновидности мапперов будут рядом. Работа с данными, в том числе их преобразование, не является ответственностью слоя представления (слоя View в паттернах MV* или его аналога в FSD — сегмента ui).

Маршрутизация

В последние годы я работал только с React Router, поэтому рассматриваю относительно него.

Маршруты, использующие компоненты-страницы, нужны только в слое app для их инициализации в приложении, поэтому там им и место. Либо, если в слайсах слоя pages несколько страниц, то маршруты можно располагать в соответствующих слайсах и импортировать оттуда в слой app.

А вот константы URL-путей могут понадобиться в любом бизнес-слое, поэтому их зачастую помещают в слой shared, например в shared/routes.

Константы переводов

Глобальные переводы обычно хранят в слое shared — например, в shared/i18n. Если же переводы располагаются в бизнес-слайсах, то для такого случая чаще всего в официальном чате я встречал вариант размещения переводов в сегменте i18n в том же слайсе, что и компонент интерфейса.

Заключение

В статье я собрал основные моменты, вызывавшие вопросы и разногласия в проектах с FSD, в которых мне довелось работать.

Конечно же, были ситуации, когда у команды возникал вопрос, к чему относится компонент: к features или entities. Я не стал его рассматривать, потому что нет универсального понятия, «чем считается» тот или иной элемент интерфейса в FSD. К тому же с выходом FSD 2.1 понятие «фича» изменилось.

В следующей статье я рассмотрю ещё несколько рекомендаций методологии, но уже более спорных.

Комментарии (1)


  1. dominus_augustus
    17.12.2025 09:03

    Если методология требует ответа на столько вопросов, может методология говно, не?