← назад к разделу

Раскладка по фичам — хорошее начало: код одной задачи лежит в одной папке. Но когда фич становится не пять, а сорок, а команда — не один человек, а несколько, простого деления на features/ и shared/ уже мало. Возникают вопросы, на которые «по фичам» не отвечает: может ли одна фича импортировать другую? Где живёт сущность «Товар», которую используют десять фич? Куда положить шапку сайта, которая собирает в себя несколько фич сразу?

Feature-Sliced Design (FSD) — это методология, которая даёт на эти вопросы одинаковые ответы для всей команды. Она добавляет к идее «по фичам» два уровня порядка: слои (кто на кого может ссылаться) и сегменты (что где лежит внутри). Дальше — по шагам.

Три оси: слои, слайсы, сегменты

FSD режет проект по трём осям сразу.

  • Слои — верхний уровень. Их фиксированный набор, и у них есть иерархия: верхние слои могут использовать нижние, но не наоборот.
  • Слайсы — деление внутри слоя по домену: entities/user, entities/product, features/add-to-cart. Имена слайсов придумываете вы, под свою предметную область.
  • Сегменты — деление внутри слайса по технической роли: ui/, model/, api/, lib/.

Путь до файла читается как адрес: features/add-to-cart/ui/AddButton.tsx — «фича добавления в корзину, её визуальная часть, кнопка». По одному пути понятно, что это и на каком уровне живёт.

Слои — снизу вверх

Слоёв шесть. Разберём от самого нижнего (ни от кого не зависит) к верхнему (собирает всё вместе).

app        ← точка сборки: роутинг, провайдеры, глобальные стили
pages      ← страницы: собирают виджеты и фичи под конкретный URL
widgets    ← крупные самостоятельные блоки (шапка, карточка товара)
features   ← действия пользователя (добавить в корзину, оставить отзыв)
entities   ← бизнес-сущности (Товар, Пользователь, Заказ)
shared     ← общее без привязки к домену (UI-кит, хелперы, конфиг)
  • shared/ — то, что не знает ничего о вашем бизнесе: примитивные кнопки и поля, обёртки над сетью, форматтеры дат. Отсюда берут все.
  • entities/ — бизнес-сущности: как выглядит Товар, его типы, запрос за одним товаром, карточка для отображения. Сущность ничего не «делает» — она про данные и их показ.
  • features/действия над сущностями: «добавить в корзину», «оставить отзыв», «применить промокод». Фича — это законченный кусочек ценности для пользователя.
  • widgets/ — крупные блоки, которые склеивают несколько фич и сущностей в самостоятельный виджет: шапка сайта, боковая панель, большая карточка товара с кнопкой покупки.
  • pages/ — страницы под конкретный маршрут: собирают виджеты и фичи в то, что видит пользователь по адресу /product/42.
  • app/ — единственная точка сборки: роутер, провайдеры, глобальные стили, инициализация. Всё приложение стартует отсюда.

Слайсы — деление по домену

Внутри entities/, features/, widgets/, pages/ код делят на слайсы — по предметной области:

entities/
  product/        ← всё про сущность «Товар»
  user/           ← всё про «Пользователя»
features/
  add-to-cart/    ← фича «добавить в корзину»
  leave-review/   ← фича «оставить отзыв»

Слайс — это замкнутый мирок одной темы. Правило простое: слайс не лезет во внутренности соседнего слайса того же слоя. Фича add-to-cart не импортирует напрямую из фичи leave-review. Если им нужно что-то общее — это общее спускается на слой ниже (в entities или shared).

Сегменты — деление по роли

Внутри слайса код раскладывают по сегментам — техническим ролям:

features/add-to-cart/
  ui/       ← компоненты (кнопка, форма)
  model/    ← состояние, логика, хуки
  api/      ← запросы к серверу
  lib/      ← вспомогательные функции слайса
  index.ts  ← публичный интерфейс слайса

Сегменты необязательны все сразу: у простой фичи может быть только ui/ и model/. Важно, что роль файла видна по папке, а не по догадке.

Правило импортов — главное в FSD

Вся строгость FSD держится на одном правиле:

Слой может импортировать только из слоёв ниже себя.

features берёт из entities и shared, но не из widgets или pages. entities берёт только из shared. shared не берёт ни у кого. Это делает зависимости однонаправленными: нижние слои не знают о верхних, поэтому их легко переиспользовать и тестировать, а изменение страницы не задевает сущность.

Второе правило — публичный интерфейс. У каждого слайса есть index.ts, через который наружу торчит только то, что можно использовать. Остальное — внутренняя кухня слайса:

// features/add-to-cart/index.ts
export { AddToCartButton } from './ui/AddToCartButton'
// model, api, lib наружу не выставлены — это детали реализации

Импортируют всегда из слайса целиком, а не из его файлов напрямую:

import { AddToCartButton } from 'features/add-to-cart'   // да
import { AddToCartButton } from 'features/add-to-cart/ui/AddToCartButton'  // нет

Оба правила несложно проверять автоматически — для FSD есть ESLint-плагин, который не даст импортировать «вверх» или мимо публичного интерфейса.

Небольшой пример

Кнопка «Купить» на странице товара маркетплейса разложится так:

  • entities/product — тип Product, запрос getProduct(id), компонент ProductCard для отображения.
  • features/add-to-cart — кнопка AddToCartButton и логика добавления; использует тип Product из entities.
  • widgets/product-panel — большая панель: ProductCard из entities + AddToCartButton из features.
  • pages/product — страница /product/:id, которая ставит на место product-panel и подгружает данные.
  • app — роутер связывает /product/:id с этой страницей.

Зависимости идут строго вниз: страница знает о виджете, виджет — о фиче и сущности, фича — о сущности, сущность — только о shared. Обратных стрелок нет.

Когда FSD оправдан, а когда нет

FSD — это дисциплина, и как у любой дисциплины, у неё есть цена: больше папок и церемоний на старте.

  • Оправдан, когда приложение большое и живёт долго, над ним работает несколько человек, фичи пересекаются и переиспользуются. Тогда единые правила экономят часы споров «куда это положить» и предотвращают клубок зависимостей.
  • Избыточен для маленького приложения на пару экранов или прототипа. Там простой раскладки по фичам достаточно, а шесть слоёв только мешают.

Хорошая новость: переход плавный. Можно начать с shared / entities / features и добавить widgets / pages, когда экранов станет много. FSD не требует всё сразу.

Коротко

  • FSD режет проект по трём осям: слои (иерархия), слайсы (домены), сегменты (роли: ui/model/api/lib).
  • Шесть слоёв снизу вверх: shared → entities → features → widgets → pages → app.
  • Главное правило: импортировать можно только из слоёв ниже — зависимости однонаправленные.
  • Каждый слайс отдаёт наружу только index.ts (публичный интерфейс); внутренности скрыты.
  • Слайсы одного слоя не лезут друг в друга — общее спускают на слой ниже.
  • FSD оправдан для крупных долгоживущих приложений; для маленьких хватит раскладки по фичам.

Что почитать дальше

  • Структура React+TypeScript-проекта — раскладка по фичам, с которой начинается путь к FSD.
  • Архитектурные подходы фронтенда — где FSD среди других способов организовать код.
  • Состояние — где в слоях FSD живёт состояние и когда оно локальное, а когда общее.
  • Компоненты — как строить компоненты с правильными границами внутри слайсов.