290 lines
12 KiB
Markdown
290 lines
12 KiB
Markdown
|
|
---
|
|||
|
|
title: Модули
|
|||
|
|
description: Структура модуля, типы (UI, бизнес, инфра), публичный API, отличие модуля от компонента
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Модули
|
|||
|
|
|
|||
|
|
Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом.
|
|||
|
|
|
|||
|
|
## Определение
|
|||
|
|
|
|||
|
|
**Модуль — минимальная архитектурная единица SLM. Он живёт на одном из слоёв, владеет конкретной областью ответственности и предоставляет наружу только публичный API.**
|
|||
|
|
|
|||
|
|
Модуль может содержать всё, что нужно этой области: компоненты, вложенные модули, хуки, сторы, сервисы, типы, стили, конфиги и утилиты. Набор сегментов не фиксирован — модуль включает только то, что реально нужно.
|
|||
|
|
|
|||
|
|
Модуль не обязан быть UI-блоком. Это может быть страница, виджет, бизнес-домен, инфраструктурный сервис или UI-kit сущность.
|
|||
|
|
|
|||
|
|
Главная граница модуля — не папка, а ответственность.
|
|||
|
|
|
|||
|
|
## Компонент
|
|||
|
|
|
|||
|
|
**Компонент — презентационная единица модуля, которая находится только в `ui/` своего родительского модуля и отвечает за отображение части интерфейса.**
|
|||
|
|
|
|||
|
|
Компонент не является архитектурной единицей: он не владеет сценарием, зависимостями, данными или внутренней структурой. Он работает только внутри границы родительского модуля.
|
|||
|
|
|
|||
|
|
> Компонент отображает. Модуль организует.
|
|||
|
|
|
|||
|
|
Компонент не может:
|
|||
|
|
|
|||
|
|
- Импортировать код проекта за пределами родительского модуля.
|
|||
|
|
- Владеть архитектурными зависимостями.
|
|||
|
|
- Содержать любые компоненты.
|
|||
|
|
- Содержать любые модули.
|
|||
|
|
- Делать внешние запросы.
|
|||
|
|
- Самостоятельно получать данные.
|
|||
|
|
- Выбирать источник данных.
|
|||
|
|
- Композировать данные.
|
|||
|
|
- Вызывать сценарные хуки.
|
|||
|
|
- Оркестрировать сценарий.
|
|||
|
|
- Композировать модули.
|
|||
|
|
- Решать, как устроен процесс.
|
|||
|
|
- Содержать бизнес-логику.
|
|||
|
|
- Содержать сценарную логику.
|
|||
|
|
|
|||
|
|
Если компоненту требуется что-то из этого списка, он перестаёт быть компонентом и должен быть оформлен как модуль.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
auth/
|
|||
|
|
├── ui/
|
|||
|
|
│ └── logout-button/
|
|||
|
|
│ ├── logout-button.tsx
|
|||
|
|
│ ├── styles/
|
|||
|
|
│ │ └── logout-button.module.css
|
|||
|
|
│ ├── types/
|
|||
|
|
│ │ └── logout-button-props.type.ts
|
|||
|
|
│ └── index.ts
|
|||
|
|
└── index.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Что считается модулем
|
|||
|
|
|
|||
|
|
Модулем считается папка, которая представляет самостоятельную область ответственности и имеет публичную границу.
|
|||
|
|
|
|||
|
|
Примеры модулей:
|
|||
|
|
|
|||
|
|
- `screens/home/` — модуль страницы.
|
|||
|
|
- `widgets/page-heading/` — модуль виджета.
|
|||
|
|
- `business/auth/` — модуль бизнес-домена.
|
|||
|
|
- `infra/theme/` — модуль инфраструктурного сервиса.
|
|||
|
|
- `ui/button/` — модуль UI-kit сущности.
|
|||
|
|
- `screens/home/parts/hero-section/` — вложенный модуль страницы.
|
|||
|
|
|
|||
|
|
Не считаются модулями:
|
|||
|
|
|
|||
|
|
- `ui/`, `parts/`, `hooks/`, `types/`, `styles/`, `config/` — это сегменты.
|
|||
|
|
- `screens/shop/`, `business/commerce/` — это группы, если в них нет `index.ts`.
|
|||
|
|
- `screens/home/ui/user-card/` — это компонент, если он находится в `ui/` и соблюдает ограничения компонента.
|
|||
|
|
|
|||
|
|
## Типы модулей
|
|||
|
|
|
|||
|
|
Тип модуля определяет обязательный корневой файл и стартовую структуру.
|
|||
|
|
|
|||
|
|
### UI-модуль
|
|||
|
|
|
|||
|
|
Модуль строится вокруг основного UI-компонента и обязан иметь основной `.tsx` файл в корне:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
header/
|
|||
|
|
├── header.tsx
|
|||
|
|
└── index.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`ui/` внутри такого модуля используется только для компонентов, которые помогают корневому `.tsx` файлу.
|
|||
|
|
|
|||
|
|
### Бизнес-модуль
|
|||
|
|
|
|||
|
|
Бизнес-модуль — модуль, который строится вокруг публичного runtime API.
|
|||
|
|
|
|||
|
|
Бизнес-модуль обязан иметь фабрику в корне:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
auth/
|
|||
|
|
├── auth.factory.ts
|
|||
|
|
├── index.ts
|
|||
|
|
└── types/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Фабрика возвращает публичный runtime API модуля.
|
|||
|
|
|
|||
|
|
### Инфраструктурный модуль
|
|||
|
|
|
|||
|
|
Инфраструктурный модуль — модуль, который строится вокруг технического сервиса или интеграции.
|
|||
|
|
|
|||
|
|
Инфраструктурный модуль не обязан иметь фиксированный корневой файл. Его структура определяется природой сервиса.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
theme/
|
|||
|
|
├── index.ts
|
|||
|
|
├── config/
|
|||
|
|
├── hooks/
|
|||
|
|
├── styles/
|
|||
|
|
└── ui/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
backend-api/
|
|||
|
|
├── backend-api.client.ts
|
|||
|
|
├── config/
|
|||
|
|
├── types/
|
|||
|
|
└── index.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Структура
|
|||
|
|
|
|||
|
|
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль включает только те части, которые нужны его ответственности.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
{module-name}/
|
|||
|
|
├── {module-name}.factory.ts # фабрика (для business-модулей)
|
|||
|
|
├── {module-name}.tsx # корневой файл модуля (опционален)
|
|||
|
|
├── ui/ # компоненты модуля
|
|||
|
|
├── parts/ # вложенные модули
|
|||
|
|
├── hooks/ # хуки
|
|||
|
|
├── stores/ # сторы состояния
|
|||
|
|
├── services/ # внешние источники данных
|
|||
|
|
├── mappers/ # трансформация данных между форматами
|
|||
|
|
├── types/ # типы
|
|||
|
|
├── styles/ # стили
|
|||
|
|
├── lib/ # утилиты модуля
|
|||
|
|
├── config/ # константы и конфигурация
|
|||
|
|
└── index.ts # публичный API
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Подробное описание сегментов — в разделе [Сегменты](/architecture/segments).
|
|||
|
|
|
|||
|
|
## Публичный API
|
|||
|
|
|
|||
|
|
Внешний код импортирует модуль только через публичный API.
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// Хорошо
|
|||
|
|
import { customerFactory } from '@/business/customer'
|
|||
|
|
import type { Customer } from '@/business/customer'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// Плохо
|
|||
|
|
import { validateToken } from '@/business/auth/lib/tokens'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`index.ts` модуля не обязан экспортировать всё содержимое. Он экспортирует только то, что действительно нужно снаружи.
|
|||
|
|
|
|||
|
|
Внутренние сегменты модуля остаются деталями реализации.
|
|||
|
|
|
|||
|
|
Business-модуль экспортирует из `index.ts` только фабрику и type-only экспорты. Хуки, компоненты, сервисы, мапперы и утилиты напрямую из `index.ts` не экспортируются — они доступны через API, который возвращает фабрика.
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// business/customer/index.ts
|
|||
|
|
export { customerFactory } from './customer.factory'
|
|||
|
|
|
|||
|
|
export type { Customer } from './types/customer.type'
|
|||
|
|
export type { CustomerApi } from './types/customer-api.type'
|
|||
|
|
export type { CustomerDeps } from './types/customer-deps.type'
|
|||
|
|
export type { CustomerFactory } from './types/customer-factory.type'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Фабрика
|
|||
|
|
|
|||
|
|
Business-модуль всегда экспортирует фабрику. Фабрика лежит в корне модуля (`{name}.factory.ts`), типизируется через `{Name}Factory` и возвращает публичный runtime API модуля.
|
|||
|
|
|
|||
|
|
Всё, что нужно внешнему коду в runtime, должно быть частью API, который возвращает фабрика.
|
|||
|
|
|
|||
|
|
Модуль без cross-domain зависимостей экспортирует фабрику без аргументов. Модуль с зависимостями — фабрику, принимающую зависимости доменными именами. Типы всегда экспортируются напрямую через `export type` — `import type` не является runtime-зависимостью.
|
|||
|
|
|
|||
|
|
Компоновка фабрик происходит на уровне модуля-потребителя: screen, layout, widget или любой другой модуль группы «Композиция».
|
|||
|
|
|
|||
|
|
### Структура business-модуля
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
business/customer/
|
|||
|
|
├── customer.factory.ts
|
|||
|
|
├── index.ts
|
|||
|
|
└── types/
|
|||
|
|
├── customer.type.ts
|
|||
|
|
├── customer-api.type.ts
|
|||
|
|
├── customer-deps.type.ts
|
|||
|
|
└── customer-factory.type.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Типы
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// business/customer/types/customer-api.type.ts
|
|||
|
|
export type CustomerApi = {
|
|||
|
|
useCustomer: () => Customer
|
|||
|
|
CustomerCard: (props: CustomerCardProps) => ReactNode
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// business/order/types/order-deps.type.ts
|
|||
|
|
export type OrderDeps = {
|
|||
|
|
customer: Pick<CustomerApi, 'useCustomer'>
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// business/order/types/order-factory.type.ts
|
|||
|
|
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Фабрика без зависимостей
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// business/customer/customer.factory.ts
|
|||
|
|
import type { CustomerFactory } from './types/customer-factory.type'
|
|||
|
|
|
|||
|
|
export const customerFactory: CustomerFactory = () => {
|
|||
|
|
return {
|
|||
|
|
useCustomer,
|
|||
|
|
CustomerCard,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Фабрика с зависимостями
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// business/order/order.factory.ts
|
|||
|
|
import type { OrderFactory } from './types/order-factory.type'
|
|||
|
|
|
|||
|
|
export const orderFactory: OrderFactory = (deps) => {
|
|||
|
|
return {
|
|||
|
|
useOrder,
|
|||
|
|
OrderCard,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Композиция на уровне screen
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// screens/home/home.screen.tsx
|
|||
|
|
import { customerFactory } from '@/business/customer'
|
|||
|
|
import { orderFactory } from '@/business/order'
|
|||
|
|
|
|||
|
|
const customer = customerFactory()
|
|||
|
|
const order = orderFactory({ customer })
|
|||
|
|
|
|||
|
|
const { useOrder, OrderCard } = order
|
|||
|
|
|
|||
|
|
export const HomeScreen = () => {
|
|||
|
|
const currentOrder = useOrder()
|
|||
|
|
|
|||
|
|
return <OrderCard order={currentOrder} />
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Жизненный цикл
|
|||
|
|
|
|||
|
|
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
|
|||
|
|
|
|||
|
|
- Нужен на одной странице → `screens/{name}/parts/`
|
|||
|
|
- Появился в 2+ местах → поднимается по природе:
|
|||
|
|
- абстрактный UI → `ui/`
|
|||
|
|
- блок с данными/логикой → `widgets/`
|
|||
|
|
- представление бизнес-домена → `business/{area}/parts/`
|
|||
|
|
|
|||
|
|
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|