Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
FSD архитектура (Feature-Sliced Design)
FSD (Feature-Sliced Design) - это современная архитектура организации кода в frontend приложениях, разработанная специально для масштабируемых React/Vue приложений. Она решает проблему быстрого роста сложности кодовой базы.
Проблема без правильной архитектуры
Обычная структура растет хаотично:
src/
components/
Button.tsx
Header.tsx
Footer.tsx
UserProfile.tsx
ProductCard.tsx
... 50+ файлов
pages/
Home.tsx
Products.tsx
utils/
api.ts
helpers.ts
... много всего
hooks/
... куча хуков
Проблемы:
- Непонятна структура
- Сложно найти нужный файл
- Когда менять компонент - неясно, что еще сломается
- Невозможна переиспользуемость
FSD решение
FSD структурирует код по "слайсам" (features) с четкими уровнями абстракции:
src/
shared/ # Переиспользуемое везде
ui/ # UI компоненты (Button, Input)
lib/ # Функции утилиты (formatDate, cn)
config/ # Конфигурация приложения
entities/ # Бизнес-сущности (User, Product, Post)
user/
ui/
UserCard.tsx
model/
userSlice.ts
userAPI.ts
index.ts
product/
ui/
ProductCard.tsx
model/
productAPI.ts
index.ts
features/ # Функции (AddToCart, Login, Search)
auth/
ui/
LoginForm.tsx
model/
authSlice.ts
index.ts
search/
ui/
SearchBar.tsx
model/
searchAPI.ts
pages/ # Страницы (маршруты)
home/
HomePage.tsx
products/
ProductsPage.tsx
app/ # Инициализация приложения
App.tsx
Router.tsx
Четыре слоя FSD
1. Shared (Общий)
Код переиспользуемый во всем приложении:
// src/shared/ui/Button.tsx
export interface ButtonProps {
children: React.ReactNode;
onClick?: () => void;
variant?: 'primary' | 'secondary';
}
export function Button({ children, variant = 'primary', onClick }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{children}
</button>
);
}
// src/shared/lib/utils.ts
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('ru-RU').format(date);
}
export function cn(...classes: (string | undefined)[]): string {
return classes.filter(Boolean).join(' ');
}
Правило: shared не знает о features и entities.
2. Entities (Сущности)
Бизнес-модели - User, Product, Post, Comment:
// src/entities/product/model/types.ts
export interface Product {
id: string;
name: string;
price: number;
rating: number;
image: string;
}
// src/entities/product/ui/ProductCard.tsx
import { Product } from '../model/types';
export function ProductCard({ product }: { product: Product }) {
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price}</p>
<span>{product.rating}/5</span>
</div>
);
}
// src/entities/product/index.ts - экспортируем API
export type { Product };
export { ProductCard };
export { useProduct } from './model/useProduct';
Правило: entities содержат модель данных и UI. Не содержат бизнес-логику конкретного flow.
3. Features (Функциональность)
Визуальные взаимодействия - Login, AddToCart, Search:
// src/features/add-to-cart/model/addToCartAPI.ts
export async function addToCart(productId: string, quantity: number) {
const response = await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId, quantity }),
});
return response.json();
}
// src/features/add-to-cart/ui/AddToCartButton.tsx
import { useState } from 'react';
import { Button } from '@/shared/ui';
import { addToCart } from '../model/addToCartAPI';
export function AddToCartButton({ productId }: { productId: string }) {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
try {
await addToCart(productId, 1);
alert('Added to cart!');
} finally {
setLoading(false);
}
};
return (
<Button onClick={handleClick} disabled={loading}>
{loading ? 'Adding...' : 'Add to Cart'}
</Button>
);
}
// src/features/add-to-cart/index.ts
export { AddToCartButton } from './ui/AddToCartButton';
Правило: features используют entities и shared, но не знают друг о друге.
4. Pages (Страницы)
Маршруты - Home, Products, Cart:
// src/pages/products/ProductsPage.tsx
import { useEffect, useState } from 'react';
import { ProductCard } from '@/entities/product';
import { AddToCartButton } from '@/features/add-to-cart';
import { SearchBar } from '@/features/search';
import { fetchProducts } from '@/shared/lib/api';
export function ProductsPage() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetchProducts().then(setProducts);
}, []);
return (
<div>
<SearchBar />
<div className="products-grid">
{products.map(product => (
<div key={product.id}>
<ProductCard product={product} />
<AddToCartButton productId={product.id} />
</div>
))}
</div>
</div>
);
}
Правило: pages комбинируют features и entities для создания полной страницы.
Правила зависимостей FSD
Стрелка показывает направление зависимостей:
page → feature → entity → shared
↗ ↗ ↗
(могут пропускать слои если нужно)
Запрещено:
- shared импортирует что-то еще
- entity импортирует feature
- feature импортирует другую feature
- page импортирует из shared напрямую (должно идти через entity/feature)
// Плохо - нарушение правил
// src/features/auth/ui/LoginForm.tsx
import { SearchBar } from '@/features/search'; // Нельзя! Features не знают друг о друге
// Хорошо
// src/pages/home/HomePage.tsx
import { LoginForm } from '@/features/auth';
import { SearchBar } from '@/features/search';
// Page может импортировать обе feature
Структура slice (слайса)
Каждый slice (feature или entity) имеет стандартную структуру:
features/add-to-cart/
ui/ # Компоненты
AddToCartButton.tsx
model/ # Логика и API
addToCartAPI.ts
useAddToCart.ts
lib/ # Утилиты этого slice
constants.ts
index.ts # Public API слайса
// src/features/add-to-cart/index.ts - Экспортируем только нужное!
export { AddToCartButton } from './ui/AddToCartButton';
export type { CartItem } from './model/types';
// НЕ экспортируем API функции
Плюсы FSD
- Масштабируемость - новые разработчики сразу понимают структуру
- Переиспользуемость - features не зависят друг от друга
- Изоляция - изменение одного slice не сломает другой
- Отсутствие циклических зависимостей - четкие правила
- Тестируемость - каждый slice можно тестировать отдельно
Минусы FSD
- Кривая обучения - нужно понять правила
- Много папок - структура глубокая
- Может быть оверинжинирингом для маленького проекта
- Требует дисциплины - легко нарушить правила
Когда использовать FSD
- Используй для enterprise приложений (50+ компонентов)
- Используй для team проектов
- Не используй для маленьких проектов (landing, todo)
Эмпирическое правило
Если в проекте:
- Менее 5 страниц → простая структура
- 5-20 страниц → рассмотри FSD
- Более 20 страниц → FSD обязательна
Вывод
FSD - это best practice для организации больших frontend приложений. Она решает проблему хаоса в растущей кодовой базе и позволяет команде эффективно работать над проектом.