Skip to content
Иван Ткаченко Блог
EN
FSD: когда папка components превращается в свалку — Иван Ткаченко ← К статьям
Architecture

FSD: когда папка components превращается в свалку

Представьте ситуацию: пять разработчиков, год работы, 300+ файлов в папке components. При каждой новой задаче приходится заново вспоминать что где лежит и какой компонент за что отвечает. Часто становится непонятно, куда класть новый файл, возникает путаница с импортами, и часть кода одной фичи перетекает в другую.

Проблема

Так может выглядеть проект через год без продуманной структуры:

components/
  Button.tsx
  Header.tsx
  HeaderNew.tsx
  Modal.tsx
  ModalConfirm.tsx
  OrderCard.tsx
  OrderList.tsx
  UserCard.tsx
  UserCardAdmin.tsx
  ...и ещё 300+ файлов

store/
  orderStore.ts
  ...30+ файлов

utils/
  orderUtils.ts
  ...100+ файлов

Что такое FSD

Один из вариантов улучшить ситуацию — разложить компоненты по папкам-фичам.

До:

components/
  OrderCard.tsx
  OrderList.tsx
  UserCard.tsx
  UserAvatar.tsx

store/
  orderStore.ts

utils/
  orderUtils.ts

После:

orders/
  OrdersPage.tsx
  OrderCard.tsx
  orderStore.ts
  orderUtils.ts

users/
  UserCard.tsx
  UserAvatar.tsx

Шесть слоёв

Feature-Sliced Design — та же идея с папками-фичами, но с чёткими правилами: шесть слоёв, у каждого своя зона ответственности, и строгий порядок кто у кого может импортировать.

app       провайдеры, роутинг, глобальные стили
pages     страница целиком: catalog, orders, profile
widgets   готовые блоки: header, product-card, sidebar
features  действия пользователя: auth-by-email, add-to-cart
entities  бизнес-сущности: user, product, order
shared    утилиты, UI-kit, api-клиент

shared

Сюда кладётся всё, что не знает про бизнес: Button, Input, formatDate, api-клиент. Если компонент знает про User или Order, это уже другой слой.

shared/
  ui/
    Button.tsx
    Input.tsx
  lib/
    formatDate.ts
    debounce.ts
  api/
    client.ts

entities

Бизнес-сущности без логики действий. Order тут просто описан, place-order уже в features.

entities/
  order/
    model/
      types.ts
      store.ts
    ui/
      OrderCard.tsx

features

Действия пользователя. Если в названии нет глагола, скорее всего это не feature, а entity.

features/
  place-order/
    ui/
      PlaceOrderButton.tsx
    model/
      placeOrder.ts
  cancel-order/
    ui/
      CancelOrderButton.tsx
    model/
      cancelOrder.ts

widgets

Готовые блоки страницы, которые комбинируют entities и features. OrderList знает про карточку заказа и кнопку отмены.

widgets/
  order-list/
    ui/
      OrderList.tsx
// OrderList.tsx
import { OrderCard } from "@/entities/order";
import { CancelOrderButton } from "@/features/cancel-order";

pages и app

pages собирают виджеты под конкретный маршрут и больше ничего не делают. app знает про всё, поэтому он сверху.

pages/
  orders/
    ui/
      OrdersPage.tsx

app/
  providers/  // Redux, Theme, QueryClient
  router/
  styles/

Правило импортов

Импорт идёт только сверху вниз. Нижние слои не знают о верхних. При расслоении существующего проекта именно нарушения этого правила подсвечивают проблемные места. Если импорт полез вверх, значит код лежит не в том слое.

app

pages

widgets

features

entities

shared

Куда положить новый файл

Начинаем с shared и идём вверх до первого совпадения. Если не подходит ни один слой, скорее всего файл делает сразу несколько вещей.

Это вообще про бизнес?
  Нет → shared

Это сущность без действий?
  → entities

Это действие пользователя?
  → features

Это блок из нескольких частей?
  → widgets

Это под конкретный маршрут?
  → pages

Не обязательно использовать все шесть слоёв

Можно начать с двух: app и shared. Постепенно подключить pages, остальное переложить в widgets и распиливать на entities и features, когда в widgets накапливается бизнес-логика и они разрастаются.

app/
  providers/
  router/

shared/
  ui/
  api/

widgets/
  order-list/
  header/

pages/
  orders/
  home/

Уже лучше, чем одна папка components на 300+ файлов.