Когда я только начинал свою карьеру фронтенд-разработчика, часто сталкивался с проблемами поддержки кода в проектах. Со временем я понял, что структура кода имеет решающее значение. Так я узнал о Feature-Sliced Design. Этот подход помогает разбивать проект на функциональные части, что упрощает работу с кодом и его сопровождение. Давайте разберемся как это работает.
Основные принципы Feature-Sliced Design
FSD (Feature-Sliced Design) нужен для удобной организации кода, особенно в больших проектах, и даёт несколько ключевых преимуществ:
1. Понятность: код разбит на независимые модули (например, авторизация, профиль), что делает структуру логичной и облегчает навигацию.
2. Поддерживаемость: каждый модуль ограничен своей зоной ответственности, что упрощает изменения — работа над одной фичей не ломает другую.
3. Переиспользуемость: изолированные компоненты и логика могут быть легко использованы в других частях приложения без дублирования.
4. Масштабируемость: новые функции можно добавлять как отдельные модули, не нарушая структуру кода.
5. Удобство тестирования: с четкими границами модулей проще писать и поддерживать тесты.
Описание структуры слоев и папок
1. App
-
Назначение: Слой для инициализации приложения.
-
Содержит: Глобальные настройки (например, темы), роутинг, провайдеры контекста.
-
Пример:
App.tsx, AppRouter.tsx
.
2. Entities
-
Назначение: Здесь хранятся бизнес-сущности — основные модели и их логика.
-
Содержит: Определения сущностей (например, User, Product), бизнес-логику, которая их касается.
-
Пример:
entities/User, entities/Product
.
3. Features
-
Назначение: Модули, которые реализуют конкретные пользовательские действия.
-
Содержит: Компоненты, хуки и логику, которая выполняет задачу для пользователя, например, авторизацию или добавление товара в корзину.
-
Пример:
features/Login, features/AddToCart
.
4. Shared
-
Назначение: Общие утилиты, типы и компоненты, которые используются в разных частях приложения.
-
Содержит: Переиспользуемые компоненты (например, кнопки), утилиты, глобальные типы.
-
Пример:
shared/Button, shared/hooks, shared/utils
.
5. Pages
-
Назначение: Собирает все компоненты, чтобы сформировать страницы приложения.
-
Содержит: Страницы, которые используют features, entities и shared слои, чтобы создавать полноценные представления.
-
Пример:
pages/HomePage, pages/ProductPage
.
6. Widgets
-
Назначение: Крупные, повторяющиеся блоки, которые можно переиспользовать на разных страницах.
-
Содержит: Модули с логикой и UI (например, блоки новостей, карусели).
-
Пример:
widgets/NewsCarousel, widgets/UserProfile
.
7. Processes (опционально)
-
Назначение: Сюда можно выносить сложные процессы, включающие несколько фич.
-
Содержит: Бизнес-процессы, если такие есть (например, процесс оформления заказа).
-
Пример:
processes/Checkout
.
Пример структуры React-приложения для Интернет-магазина:
src/ ├── app/ // Глобальные настройки приложения │ ├── store.js // Настройка Redux store, подключение middleware и т.д. │ └── rootReducer.js // Главный редьюсер, который объединяет все слайсы │ ├── pages/ // Основные страницы приложения │ ├── HomePage/ // Главная страница │ │ ├── index.js // Точка входа страницы для упрощённого импорта │ │ ├── HomePage.jsx // Компонент главной страницы │ │ └── HomePage.module.css // Стили для главной страницы │ ├── ProductPage/ // Страница деталей товара │ │ ├── index.js │ │ ├── ProductPage.jsx │ │ └── ProductPage.module.css │ ├── CartPage/ // Страница корзины │ │ ├── index.js │ │ ├── CartPage.jsx │ │ └── CartPage.module.css │ └── CheckoutPage/ // Страница оформления заказа │ ├── index.js │ ├── CheckoutPage.jsx │ └── CheckoutPage.module.css │ ├── widgets/ // Повторяющиеся UI-блоки, используемые на нескольких страницах │ ├── Header/ // Шапка сайта │ │ ├── index.js │ │ ├── Header.jsx │ │ └── Header.module.css │ ├── Footer/ // Подвал сайта │ │ ├── index.js │ │ ├── Footer.jsx │ │ └── Footer.module.css │ └── ProductList/ // Виджет со списком товаров │ ├── index.js │ ├── ProductList.jsx │ └── ProductList.module.css | ├── features/ // Конкретные функции приложения, каждая из которых автономна │ ├── Product/ // Функционал работы с товарами │ │ ├── index.js // Экспортирует компоненты и логику фичи │ │ ├── ProductSlice.js // Redux slice для управления состоянием товаров │ │ └── Product.module.css │ ├── Cart/ // Функционал работы с корзиной │ │ ├── index.js │ │ ├── CartSlice.js // Redux slice для управления состоянием корзины │ │ └── Cart.module.css │ └── Auth/ // Функционал авторизации пользователя │ ├── index.js │ ├── AuthSlice.js // Redux slice для состояния пользователя (авторизация, токены и т.д.) │ └── Auth.module.css │ ├── processes/ // Сложные бизнес-процессы, объединяющие фичи и виджеты │ ├── UserRegistration/ // Процесс регистрации пользователя │ │ ├── index.js │ │ ├── UserRegistration.jsx // Компонент регистрации с формами и валидацией │ │ └── UserRegistration.module.css │ ├── AddToCart/ // Процесс добавления товара в корзину │ │ ├── index.js │ │ ├── AddToCart.jsx // Компонент добавления в корзину, включает логику для Cart │ │ └── AddToCart.module.css │ └── CheckoutProcess/ // Процесс оформления заказа │ ├── index.js │ ├── CheckoutProcess.jsx // Компонент оформления заказа с интеграцией оплаты │ └── CheckoutProcess.module.css │ ├── shared/ // Общие компоненты, которые используются по всему проекту │ └── components/ │ ├── Button/ // Кнопка, переиспользуемая по всему приложению │ │ ├── index.js │ │ ├── Button.jsx │ │ └── Button.module.css │ ├── Input/ // Поле ввода, переиспользуемое в формах │ │ ├── index.js │ │ ├── Input.jsx │ │ └── Input.module.css │ └── Modal/ // Модальное окно для отображения уведомлений и подтверждений │ ├── index.js │ ├── Modal.jsx │ └── Modal.module.css │ └── utils/ // Утилитарные функции и хелперы ├── api.js // API-методы для взаимодействия с сервером └── formatPrice.js // Функция для форматирования цен, чтобы они выглядели красиво
Поддержка модульности с алиасами и зависимостями
Модульная архитектура с алиасами и управлением зависимостями в FSD делает проект более структурированным и гибким для роста. Давайте разберём, как это работает и что важно учесть.
1. Алиасы для модулей
Алиасы в FSD позволяют упростить импорт, сократив длинные пути и изоляцию модулей. Это делается с помощью настройки tsconfig.json или webpack.config.js. В tsconfig.json, например, можно прописать алиасы следующим образом:
{ "compilerOptions": { "baseUrl": "src", "paths": { "@app/*": ["app/*"], "@entities/*": ["entities/*"], "@features/*": ["features/*"], "@shared/*": ["shared/*"], "@pages/*": ["pages/*"], "@widgets/*": ["widgets/*"], "@processes/*": ["processes/*"] } } }
Теперь можно импортировать зависимости в модули по короткому пути, что делает код более читаемым и поддерживаемым.
import { UserModel } from "@entities/User"; import { AddToCard } from "@feature/AddToCard";
2. Изоляция модулей
Каждый модуль в FSD представляет собой отдельный слой ответственности. Например, entities служит для работы с бизнес-логикой и сущностями, features — для пользовательских функций, shared — для общих компонентов, доступных в приложении. Это изолирует логику и данные, ограничивая влияние изменений на весь проект.
3. Управление зависимостями
Важный принцип модульной архитектуры FSD — минимизация зависимости между модулями. Здесь поможет использование инверсии зависимостей (Dependency Injection) и управляемых экспортов. Например, экспортируем только те части модулей, которые нужны в других слоях, а частные элементы (вроде вспомогательных функций) скрываем внутри модуля.
4. Настройка зависимостей и разрешений
Чтобы избежать циклических зависимостей, FSD предполагает, что:
-
Нижние слои (shared) могут быть импортированы в верхние слои (features, entities, pages).
-
Верхние слои не могут напрямую импортировать друг друга. Например, features и entities должны общаться через слой shared или API.
Пример ограничения зависимостей:
Для управления доступом и зависимостями можно использовать ESLint с настройками правил для алиасов. В .eslintrc.json можно прописать правила для блокировки циклических и ненужных зависимостей.
Пример:
{ "rules": { "no-restricted-imports": [ "error", { "paths": [ { "name": "@features", "message": "Avoid direct imports from features. Use only allowed layers." } ] } ] } }
Типы и DTO (Data Transfer Objects)
В FSD типы и DTO обеспечивают строгую структуру данных и удобство при работе с API, особенно в масштабных проектах.
Типы
Типы описывают внутренние данные приложения и помогают избежать ошибок. Например, тип для пользователя может быть таким:
Пример:
export type Order = { id: string; date: string; customer_name: string; total_amount: number; };
DTO
DTO (Data Transfer Objects) описывают данные для обмена с API и отделяют их от внутренней структуры, что упрощает работу с изменениями на сервере.
Пример:
export type OrderDTO = { id: string; date: string; customer_name: string; total_amount: number; };
Maппинг DTO к типам
Маппинг преобразует DTO в нужный формат. Это удобно, когда данные API отличаются по структуре.
Пример:
export const mapUserDtoToUser = (dto: OrderDTO): Order => ({ id: dto.id, date: dto.date, customer_name: dto.customer_name, total_amount: dto.total_amount, });
Зачем это нужно?
-
Гибкость при изменении API: Корректируем только DTO и маппинг.
-
Читаемость и строгая структура: Типы делают код понятнее.
-
Защита внутренней структуры: DTO отделяют внутренние данные от внешних запросов.
Типы и DTO повышают стабильность и гибкость в работе с данными, делая архитектуру надежной.
Заключение
FSD — мощная архитектура, которая дает проекту чёткую структуру, особенно в масштабируемых приложениях. Разделение на слои (entities, features, pages, widgets и т.д.) позволяет изолировать модули, упрощая поддержку и развитие кода.
С использованием алиасов, DTO, строгой типизации и настройки зависимостей, FSD становится гибким инструментом для организации данных и логики. Это облегчило работу для нашей команды, минимизируя ошибки, упрощая адаптацию к изменениям и делая проект удобным для дальнейшего расширения.
P.S. Статья вынесена из песочницы в связи с получением приглашения.
ссылка на оригинал статьи https://habr.com/ru/articles/857192/
Добавить комментарий