Feature-Sliced Design (FSD) это методология структуры фронтенд-проекта. Её цель: чтобы по пути файла было понятно, за что он отвечает, и чтобы зависимости шли в одну сторону. В российских компаниях FSD спрашивают часто, потому что многие проекты на ней живут.
Три уровня: слои, слайсы, сегменты
Слои (сверху вниз, актуальная версия методологии 2.x):
| Слой | Что лежит |
|---|---|
app | инициализация: провайдеры, роутер, глобальные стили |
pages | страницы целиком (в FSD 2.x допускается держать здесь и код, нужный только одной странице) |
widgets | крупные самостоятельные блоки интерфейса: шапка, лента, сайдбар |
features | пользовательские действия, которые несут ценность: «добавить в корзину», «войти» |
entities | бизнес-сущности: пользователь, товар, заказ |
shared | переиспользуемое без бизнес-смысла: UI-кит, api-клиент, утилиты, конфиг |
Слой processes объявлен устаревшим, его логику переносят в app или features.
Главное правило импортов: модуль может импортировать только из слоёв строго ниже себя. features может брать из entities и shared, но не из widgets. И слайсы одного слоя не импортируют друг друга: features/cart не лезет в features/auth.
Слайсы делят слой по бизнес-домену: entities/user, entities/product, features/add-to-cart. В app и shared слайсов нет, там сразу сегменты.
Сегменты делят слайс по техническому назначению: ui, model (стор, бизнес-логика), api, lib, config.
src/
app/
pages/product/
widgets/product-card-list/
features/add-to-cart/
ui/AddToCartButton.tsx
model/useAddToCart.ts
index.ts <- публичный API
entities/product/
ui/ProductCard.tsx
model/types.ts
api/getProduct.ts
index.ts
shared/ui/Button/
Публичный API
Каждый слайс экспортирует наружу только то, что разрешено, через index.ts. Снаружи импортируют @/entities/product, а не @/entities/product/ui/ProductCard. Тогда внутренности можно переставлять без правок по всему проекту.
Кросс-импорты сущностей
Иногда сущности ссылаются друг на друга: заказ содержит пользователя. Для этого в FSD есть нотация @x: entities/user/@x/order.ts явно экспортирует то, что разрешено брать сущности order. Это осознанное исключение, а не лазейка.
Как решить, куда положить код
- Нет бизнес-смысла (кнопка,
formatDate) →shared. - Это «что» в предметной области (товар, его тип, карточка) →
entities. - Это «действие пользователя», от которого что-то меняется →
features. - Это блок, который собирает несколько фич и сущностей →
widgets. - Используется на одной странице и нигде больше → можно держать в
pages.
Проверка соблюдения правил делается линтером: Steiger (официальный линтер FSD) или eslint-plugin-boundaries.
Частые ошибки
- Складывать всё подряд в
features: каждая кнопка становится фичей. Фича это действие с ценностью, а не любой компонент. - Импортировать из фичи в фичу. Если двум фичам нужно общее, это поднимается в
widgets(композиция) или опускается вentities/shared. - Обход публичного API глубоким импортом.
sharedпревращается в свалку с бизнес-логикой.
Что спросят на собесе
- Назови слои FSD и правило импортов между ними.
- Чем фича отличается от сущности? Приведи пример.
- Зачем публичный API у слайса?
- Что делать, если двум фичам нужно общее?
- Какие минусы у FSD?