forked from templates/nextjs-template
style: Обновлены правила код стайла
This commit is contained in:
@@ -22,9 +22,9 @@ description: Что AI-агент обязан прочитать перед н
|
|||||||
### 1. Архитектура (КРИТИЧЕСКИ ВАЖНО)
|
### 1. Архитектура (КРИТИЧЕСКИ ВАЖНО)
|
||||||
|
|
||||||
* [Архитектура: Обзор](./basics/architecture/index.md)
|
* [Архитектура: Обзор](./basics/architecture/index.md)
|
||||||
* [Архитектура: Слои](./basics/architecture/reference/layers.md)
|
* [Архитектура: Слои](./basics/architecture/layers.md)
|
||||||
* [Архитектура: Модули](./basics/architecture/reference/modules.md)
|
* [Архитектура: Модули](./basics/architecture/modules.md)
|
||||||
* [Архитектура: Сегменты](./basics/architecture/reference/segments.md)
|
* [Архитектура: Сегменты](./basics/architecture/segments.md)
|
||||||
|
|
||||||
**Архитектура — это самое важное в проекте.**
|
**Архитектура — это самое важное в проекте.**
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
- [Технологии и библиотеки](./basics/tech-stack.md) — Какие библиотеки и инструменты используются в проекте.
|
- [Технологии и библиотеки](./basics/tech-stack.md) — Какие библиотеки и инструменты используются в проекте.
|
||||||
- [Именование](./basics/naming.md) — Как называть переменные, файлы и прочие сущности в коде.
|
- [Именование](./basics/naming.md) — Как называть переменные, файлы и прочие сущности в коде.
|
||||||
- [Архитектура: Обзор](./basics/architecture/index.md) — Архитектурный подход проекта: что такое SLM и как он устроен.
|
- [Архитектура: Обзор](./basics/architecture/index.md) — Архитектурный подход проекта: что такое SLM и как он устроен.
|
||||||
- [Архитектура: Слои](./basics/architecture/reference/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны.
|
- [Архитектура: Слои](./basics/architecture/layers.md) — Из каких слоёв состоит SLM-архитектура и как они связаны.
|
||||||
- [Архитектура: Модули](./basics/architecture/reference/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен.
|
- [Архитектура: Модули](./basics/architecture/modules.md) — Что такое модуль в SLM-архитектуре и как он устроен.
|
||||||
- [Архитектура: Сегменты](./basics/architecture/reference/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
|
- [Архитектура: Сегменты](./basics/architecture/segments.md) — Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
|
||||||
- [Стиль кода](./basics/code-style.md) — Как оформляется код в проекте.
|
- [Стиль кода](./basics/code-style.md) — Как оформляется код в проекте.
|
||||||
- [Документирование](./basics/documentation.md) — Что и как документировать в коде.
|
- [Документирование](./basics/documentation.md) — Что и как документировать в коде.
|
||||||
- [Типизация](./basics/typing.md) — Как типизируется код в проекте.
|
- [Типизация](./basics/typing.md) — Как типизируется код в проекте.
|
||||||
@@ -63,4 +63,4 @@
|
|||||||
- [Biome](./applied/biome.md) — Установка и настройка линтера-форматтера в новом проекте.
|
- [Biome](./applied/biome.md) — Установка и настройка линтера-форматтера в новом проекте.
|
||||||
- [PostCSS](./applied/postcss.md) — Установка и настройка CSS-процессора в новом проекте.
|
- [PostCSS](./applied/postcss.md) — Установка и настройка CSS-процессора в новом проекте.
|
||||||
- [VS Code](./applied/vscode.md) — Единые настройки редактора и расширений для команды.
|
- [VS Code](./applied/vscode.md) — Единые настройки редактора и расширений для команды.
|
||||||
- [Локализация](./applied/localization.md) — Как организовать локализацию как infrastructure-модуль.
|
- [Локализация](./applied/localization.md) — Как организовать локализацию как infra-модуль.
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
e835210
|
eadc462
|
||||||
2026-04-30T13:02:04.343Z
|
2026-05-08T04:14:35.127Z
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Алиасы импортов
|
title: Алиасы импортов
|
||||||
description: Какие алиасы импортов есть в проекте и как ими пользоваться.
|
description: Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infrastructure, ui, shared]
|
keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infra, ui, shared]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Алиасы импортов
|
# Алиасы импортов
|
||||||
@@ -21,7 +21,7 @@ keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app,
|
|||||||
"screens/*": ["./src/screens/*"],
|
"screens/*": ["./src/screens/*"],
|
||||||
"widgets/*": ["./src/widgets/*"],
|
"widgets/*": ["./src/widgets/*"],
|
||||||
"business/*": ["./src/business/*"],
|
"business/*": ["./src/business/*"],
|
||||||
"infrastructure/*": ["./src/infrastructure/*"],
|
"infra/*": ["./src/infra/*"],
|
||||||
"ui/*": ["./src/ui/*"],
|
"ui/*": ["./src/ui/*"],
|
||||||
"shared/*": ["./src/shared/*"]
|
"shared/*": ["./src/shared/*"]
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app,
|
|||||||
- **Каждый импорт между модулями — через алиас слоя.** Относительные пути (`../../`) запрещены за пределами своего модуля.
|
- **Каждый импорт между модулями — через алиас слоя.** Относительные пути (`../../`) запрещены за пределами своего модуля.
|
||||||
- **Внутри одного модуля** допустимы относительные импорты (`./model`, `./ui/button`) — это часть инкапсуляции модуля.
|
- **Внутри одного модуля** допустимы относительные импорты (`./model`, `./ui/button`) — это часть инкапсуляции модуля.
|
||||||
- **Префикс `@/` не используется.** Имя слоя — само по себе адрес.
|
- **Префикс `@/` не используется.** Имя слоя — само по себе адрес.
|
||||||
- **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](../basics/architecture/reference/layers.md)).
|
- **Направление импортов** определяется архитектурой, не алиасами. Алиас разрешает импорт технически, но не отменяет правила слоёв (→ [Слои](../basics/architecture/layers.md)).
|
||||||
|
|
||||||
**Хорошо**
|
**Хорошо**
|
||||||
|
|
||||||
|
|||||||
@@ -1,201 +1,165 @@
|
|||||||
---
|
---
|
||||||
title: Компонент
|
title: Компонент
|
||||||
description: Как создавать React-компоненты внутри SLM-модулей.
|
description: Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Компонент
|
# Компонент
|
||||||
|
|
||||||
Как создавать React-компоненты внутри SLM-модулей.
|
Как должен выглядеть сгенерированный React-компонент внутри SLM-модуля.
|
||||||
|
|
||||||
## Назначение
|
## Назначение
|
||||||
|
|
||||||
Компонент — минимальная UI-единица проекта. Это один `.tsx` файл без собственной папки, сегментов и публичного API.
|
Архитектурное определение компонента описано в разделе [Модули → Компонент](../basics/architecture/modules.md#компонент), а структура сегмента `ui/` — в разделе [Сегменты → ui/](../basics/architecture/segments.md#сегмент-ui).
|
||||||
|
|
||||||
Компонент может использовать стили, типы, хуки и другие ресурсы родительского модуля. Наличие связанных файлов в `styles/` или `types/` не превращает компонент в модуль.
|
Эта страница не повторяет архитектурные ограничения. Она показывает, каким должен быть результат генерации компонента: структура папки, `.tsx`, типы, стили и локальный экспорт.
|
||||||
|
|
||||||
## Компонент или модуль
|
::: danger Компоненты не создаются вручную
|
||||||
|
Компоненты в проекте создаются только через кодогенератор: через [VS Code](./templates/templates-usage.md#через-vs-code) или [CLI](./templates/templates-usage.md#через-cli).
|
||||||
|
|
||||||
Классификация определяется границей владения:
|
Ручное создание компонента запрещено. Это грубое нарушение правил работы в проекте для разработчика и AI-ассистента.
|
||||||
|
|
||||||
- `component` — один `.tsx` файл внутри модуля;
|
Если в проекте нет шаблона `.templates/component`, сначала создайте шаблон по разделу [Создание шаблонов](./templates/templates-create.md), и только потом генерируйте компонент на его основе.
|
||||||
- `module` — папка с `index.ts`, сегментами и собственной публичной границей.
|
:::
|
||||||
|
|
||||||
|
## Создание
|
||||||
|
|
||||||
|
1. Проверьте, что в проекте есть шаблон `.templates/component`.
|
||||||
|
2. Если шаблона нет — создайте его по разделу [Создание шаблонов](./templates/templates-create.md).
|
||||||
|
3. Сгенерируйте компонент через [VS Code или CLI](./templates/templates-usage.md).
|
||||||
|
|
||||||
|
Структура и код ниже показывают ожидаемый результат генерации. Их нельзя использовать как инструкцию для ручного создания файлов.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
Компонент размещается в `ui/{component-name}/` родительского модуля.
|
||||||
|
|
||||||
|
Для каждого компонента обязательны `.tsx`, типы, стили и локальный `index.ts`.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
user/
|
user-card/
|
||||||
├── ui/
|
|
||||||
│ └── user-avatar.tsx # компонент
|
|
||||||
├── styles/
|
|
||||||
│ └── user-avatar.module.css # ресурс родительского модуля
|
|
||||||
├── types/
|
|
||||||
│ └── user-avatar.type.ts # ресурс родительского модуля
|
|
||||||
└── user.tsx # корневой компонент модуля
|
|
||||||
```
|
|
||||||
|
|
||||||
`user-avatar.tsx` остаётся компонентом, потому что у него нет собственной папки, `index.ts` и сегментов.
|
|
||||||
|
|
||||||
## Где размещать
|
|
||||||
|
|
||||||
Компонент размещается внутри модуля:
|
|
||||||
|
|
||||||
- В корне модуля, если это главная UI-сущность модуля.
|
|
||||||
- В `ui/`, если это вспомогательный компонент модуля.
|
|
||||||
|
|
||||||
```text
|
|
||||||
user/
|
|
||||||
├── ui/
|
|
||||||
│ └── user-avatar.tsx
|
|
||||||
├── styles/
|
|
||||||
│ ├── user.module.css
|
|
||||||
│ └── user-avatar.module.css
|
|
||||||
├── types/
|
|
||||||
│ ├── user.type.ts
|
|
||||||
│ └── user-avatar.type.ts
|
|
||||||
├── user.tsx
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
`user.tsx` — корневой компонент модуля. `ui/user-avatar.tsx` — вспомогательный компонент этого же модуля.
|
|
||||||
|
|
||||||
## Что запрещено
|
|
||||||
|
|
||||||
- Заворачивать компонент в папку: `ui/header/header.tsx`.
|
|
||||||
- Создавать для компонента отдельный `index.ts`.
|
|
||||||
- Создавать собственные сегменты внутри папки компонента: `ui/header/styles/`, `ui/header/types/`, `ui/header/hooks/` и т.п.
|
|
||||||
- Декларировать внутри `.tsx` сторы, сервисы, API-клиенты, мапперы или утилиты. Для этого есть сегменты родительского модуля.
|
|
||||||
- Размещать бизнес-правила прямо в компоненте. Компонент может использовать готовые зависимости модуля, но не определяет их.
|
|
||||||
- Размещать компонент в `parts/` напрямую. `parts/` содержит только модули.
|
|
||||||
|
|
||||||
**Плохо**
|
|
||||||
|
|
||||||
```text
|
|
||||||
user/
|
|
||||||
└── ui/
|
└── ui/
|
||||||
└── user-avatar/
|
└── user-status/
|
||||||
├── styles/
|
├── styles/
|
||||||
│ └── user-avatar.module.css
|
│ └── user-status.module.css
|
||||||
├── user-avatar.tsx
|
├── types/
|
||||||
|
│ └── user-status-props.type.ts
|
||||||
|
├── user-status.tsx
|
||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
**Хорошо**
|
|
||||||
|
|
||||||
```text
|
|
||||||
user/
|
|
||||||
├── ui/
|
|
||||||
│ └── user-avatar.tsx
|
|
||||||
├── styles/
|
|
||||||
│ └── user-avatar.module.css
|
|
||||||
└── types/
|
|
||||||
└── user-avatar.type.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Стили и типы
|
|
||||||
|
|
||||||
Компонент использует ресурсы родительского модуля.
|
|
||||||
|
|
||||||
`styles/` и `types/` рядом с корневым компонентом — это сегменты модуля, а не собственные папки `.tsx` файла.
|
|
||||||
|
|
||||||
- CSS Module компонента лежит в `styles/` родительского модуля и называется по компоненту: `user-avatar.module.css`.
|
|
||||||
- Если у компонента есть CSS Module, его корневой класс всегда называется `.root`.
|
|
||||||
- Типы компонента лежат в `types/` родительского модуля и называются по компоненту: `user-avatar.type.ts`.
|
|
||||||
- Локальный `type Props` внутри `.tsx` не используется. Типы пропсов всегда выносятся в `types/` родительского модуля.
|
|
||||||
- Экспорт типа из `types/` не делает его публичным API. Публичным он становится только при реэкспорте из `index.ts` модуля.
|
|
||||||
|
|
||||||
Причина `.root`: в DevTools проще находить корневой DOM-узел компонента и отличать его от внутренних элементов.
|
|
||||||
|
|
||||||
## Реализация
|
## Реализация
|
||||||
|
|
||||||
- Компоненты объявляются через `const`.
|
Пример ниже показывает файлы базового компонента.
|
||||||
- `React.FC` не используется.
|
|
||||||
- JSDoc-комментарий обязателен для компонента.
|
|
||||||
- Пропсы деструктурируются в теле компонента.
|
|
||||||
- `className` объединяется с `styles.root` через `cl()`.
|
|
||||||
- Побочные эффекты и состояние выносятся в хуки модуля, если перестают быть тривиальными.
|
|
||||||
- Компонент возвращает JSX и не содержит orchestration-код страницы или бизнес-домена.
|
|
||||||
|
|
||||||
`user/types/user-avatar.type.ts`
|
### Типы
|
||||||
|
|
||||||
|
Файл типов делится на три части:
|
||||||
|
|
||||||
|
- `UserStatusParams` — собственные параметры компонента. Здесь лежат только данные, которые нужны именно этому компоненту.
|
||||||
|
- `RootAttrs` — параметры корневой обёртки: `div`, `span`, `a`, `button` или другого HTML-элемента. Если компонент сам управляет `children`, они исключаются через `Omit`.
|
||||||
|
- `UserStatusProps` — итоговые пропсы компонента. Тип объединяет собственные параметры и параметры корневой обёртки.
|
||||||
|
|
||||||
|
Собственные параметры и их поля документируются по правилам раздела [Документирование → Типы, интерфейсы, enum](../basics/documentation.md#типы-интерфейсы-enum).
|
||||||
|
|
||||||
|
`user-card/ui/user-status/types/user-status-props.type.ts`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import type { ImageProps } from 'next/image'
|
import type { ComponentPropsWithoutRef } from 'react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Параметры UserAvatar.
|
* Параметры UserStatus.
|
||||||
*/
|
*/
|
||||||
export type UserAvatarParams = {}
|
export type UserStatusParams = {
|
||||||
|
/** Текст статуса пользователя. */
|
||||||
|
label: string
|
||||||
|
/** Доступен ли пользователь сейчас. */
|
||||||
|
isOnline: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/** Пропсы базового изображения. */
|
/** Атрибуты корневого элемента без children. */
|
||||||
type RootAttrs = ImageProps
|
type RootAttrs = Omit<ComponentPropsWithoutRef<'span'>, 'children'>
|
||||||
|
|
||||||
export type UserAvatarProps = RootAttrs & UserAvatarParams
|
export type UserStatusProps = RootAttrs & UserStatusParams
|
||||||
```
|
```
|
||||||
|
|
||||||
`user/ui/user-avatar.tsx`
|
### TSX
|
||||||
|
|
||||||
|
В `.tsx` лежит только сам компонент:
|
||||||
|
|
||||||
|
- Компонент объявляется через `const` и именованный экспорт.
|
||||||
|
- `React.FC` не используется.
|
||||||
|
- Параметры компонента типизируются через `Props`.
|
||||||
|
- Возвращаемый тип не указывается: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата.
|
||||||
|
- JSDoc-комментарий обязателен и пишется по правилам раздела [Документирование → Компоненты](../basics/documentation.md#компоненты).
|
||||||
|
- Пропсы деструктурируются в теле компонента, а не в сигнатуре.
|
||||||
|
- Из пропсов обязательно выделяются `className` и `...rootAttrs`.
|
||||||
|
- Функция конкатенации CSS-классов импортируется и именуется `cl`.
|
||||||
|
- Корневой CSS-класс всегда называется `.root`.
|
||||||
|
|
||||||
|
Комментарий описывает назначение и сценарии применения компонента, а не DOM-разметку или внутреннюю реализацию.
|
||||||
|
|
||||||
|
`className` — внешний CSS-класс, который родитель может передать компоненту. `rootAttrs` — остальные атрибуты корневой обёртки: `id`, `aria-*`, `data-*`, обработчики событий и другие HTML-атрибуты. Они прокидываются на корневой DOM-элемент компонента.
|
||||||
|
|
||||||
|
`.root` нужен, чтобы в DevTools быстро находить корневой DOM-узел компонента и одинаково подключать внешний `className` к реальному корню.
|
||||||
|
|
||||||
|
`user-card/ui/user-status/user-status.tsx`
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import cl from 'clsx'
|
import cl from 'clsx'
|
||||||
import Image from 'next/image'
|
import type { UserStatusProps } from './types/user-status-props.type'
|
||||||
import type { UserAvatarProps } from '../types/user-avatar.type'
|
import styles from './styles/user-status.module.css'
|
||||||
import styles from '../styles/user-avatar.module.css'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Аватар пользователя.
|
* Статус пользователя в карточке профиля.
|
||||||
*
|
*
|
||||||
* Используется для:
|
* Используется для:
|
||||||
* - отображения пользователя в карточке
|
* - отображения текущей доступности пользователя
|
||||||
* - отображения пользователя в шапке профиля
|
* - визуального выделения онлайн- и офлайн-состояний
|
||||||
*/
|
*/
|
||||||
export const UserAvatar = (props: UserAvatarProps) => {
|
export const UserStatus = (props: UserStatusProps) => {
|
||||||
const { className, ...imageProps } = props
|
const { label, isOnline, className, ...rootAttrs } = props
|
||||||
|
|
||||||
return <Image {...imageProps} className={cl(styles.root, className)} />
|
return (
|
||||||
|
<span
|
||||||
|
{...rootAttrs}
|
||||||
|
className={cl(styles.root, isOnline && styles.online, className)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`user/styles/user-avatar.module.css`
|
### Стили
|
||||||
|
|
||||||
|
`user-card/ui/user-status/styles/user-status.module.css`
|
||||||
|
|
||||||
```css
|
```css
|
||||||
.root {
|
.root {
|
||||||
display: block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.root::before {
|
||||||
|
content: '';
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
background: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.online {
|
||||||
|
color: var(--color-success);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Когда нужен модуль
|
### Локальный экспорт
|
||||||
|
|
||||||
Решение о выделении модуля остаётся за разработчиком. Поднимать компонент в модуль стоит, если он становится самостоятельной областью:
|
`user-card/ui/user-status/index.ts`
|
||||||
|
|
||||||
- получает свои вложенные компоненты;
|
```ts
|
||||||
- получает свои хуки, стор или сервисы;
|
export { UserStatus } from './user-status'
|
||||||
- получает внутренние мапперы или утилиты;
|
export type { UserStatusProps } from './types/user-status-props.type'
|
||||||
- требует собственного публичного API;
|
|
||||||
- начинает переиспользоваться вне родительского модуля;
|
|
||||||
- становится отдельной зоной параллельной разработки.
|
|
||||||
|
|
||||||
Пример: страница — это screen-модуль, а самостоятельные секции страницы — вложенные модули в `parts/`.
|
|
||||||
|
|
||||||
```text
|
|
||||||
screens/home/
|
|
||||||
├── parts/
|
|
||||||
│ ├── hero-section/
|
|
||||||
│ │ ├── styles/
|
|
||||||
│ │ │ └── hero-section.module.css
|
|
||||||
│ │ ├── types/
|
|
||||||
│ │ │ └── hero-section.type.ts
|
|
||||||
│ │ ├── hero-section.tsx
|
|
||||||
│ │ └── index.ts
|
|
||||||
│ └── features-section/
|
|
||||||
│ ├── styles/
|
|
||||||
│ │ └── features-section.module.css
|
|
||||||
│ ├── types/
|
|
||||||
│ │ └── features-section.type.ts
|
|
||||||
│ ├── features-section.tsx
|
|
||||||
│ └── index.ts
|
|
||||||
├── styles/
|
|
||||||
│ └── home.module.css
|
|
||||||
├── types/
|
|
||||||
│ └── home.type.ts
|
|
||||||
├── home.screen.tsx
|
|
||||||
└── index.ts
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`hero-section` и `features-section` — модули, потому что это самостоятельные части страницы со своей структурой и публичной точкой входа.
|
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Локализация
|
title: Локализация
|
||||||
description: Как организовать локализацию как infrastructure-модуль.
|
description: Как организовать локализацию как infra-модуль.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Локализация
|
# Локализация
|
||||||
|
|
||||||
Как организовать локализацию как infrastructure-модуль.
|
Как организовать локализацию как infra-модуль.
|
||||||
|
|
||||||
## Назначение
|
## Назначение
|
||||||
|
|
||||||
Локализация — инфраструктурная подсистема приложения. Она отвечает за текущую локаль, словари, форматирование переводов и API для компонентов.
|
Локализация — инфраструктурная подсистема приложения. Она отвечает за текущую локаль, словари, форматирование переводов и API для компонентов.
|
||||||
|
|
||||||
Код локализации живёт в `src/infrastructure/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infrastructure-модуля.
|
Код локализации живёт в `src/infra/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infra-модуля.
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/i18n/
|
src/infra/i18n/
|
||||||
├── config/
|
├── config/
|
||||||
│ └── i18n.config.ts
|
│ └── i18n.config.ts
|
||||||
├── dictionaries/
|
├── dictionaries/
|
||||||
@@ -31,16 +31,16 @@ src/infrastructure/i18n/
|
|||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infrastructure/i18n`.
|
Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infra/i18n`.
|
||||||
|
|
||||||
## Подключение
|
## Подключение
|
||||||
|
|
||||||
`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infrastructure/i18n/`.
|
`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infra/i18n/`.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/app/layout.tsx
|
// src/app/layout.tsx
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { I18nProvider } from 'infrastructure/i18n'
|
import { I18nProvider } from 'infra/i18n'
|
||||||
|
|
||||||
type RootLayoutProps = {
|
type RootLayoutProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -62,7 +62,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||||||
Компоненты получают переводы через готовый API модуля локализации:
|
Компоненты получают переводы через готовый API модуля локализации:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useTranslation } from 'infrastructure/i18n'
|
import { useTranslation } from 'infra/i18n'
|
||||||
|
|
||||||
export const ProfileTitle = () => {
|
export const ProfileTitle = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -73,9 +73,9 @@ export const ProfileTitle = () => {
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
- Локализация живёт в `infrastructure/i18n/`.
|
- Локализация живёт в `infra/i18n/`.
|
||||||
- `app/` только подключает готовый provider и передаёт locale.
|
- `app/` только подключает готовый provider и передаёт locale.
|
||||||
- Словари не импортируются напрямую в компоненты, screens или business-модули.
|
- Словари не импортируются напрямую в компоненты, screens или business-модули.
|
||||||
- Ключи переводов не собираются динамически из строк, если это ломает типизацию и поиск.
|
- Ключи переводов не собираются динамически из строк, если это ломает типизацию и поиск.
|
||||||
- Тексты интерфейса не хардкодятся в переиспользуемых компонентах, если они должны переводиться.
|
- Тексты интерфейса не хардкодятся в переиспользуемых компонентах, если они должны переводиться.
|
||||||
- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infrastructure-модуля.
|
- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infra-модуля.
|
||||||
|
|||||||
@@ -1,162 +1,156 @@
|
|||||||
---
|
---
|
||||||
title: Модуль
|
title: Модуль
|
||||||
description: Как создавать и организовывать SLM-модули в проекте.
|
description: Как должен выглядеть сгенерированный SLM-модуль в проекте.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Модуль
|
# Модуль
|
||||||
|
|
||||||
Как создавать и организовывать SLM-модули в проекте.
|
Как должен выглядеть сгенерированный SLM-модуль в проекте.
|
||||||
|
|
||||||
## Назначение
|
## Назначение
|
||||||
|
|
||||||
Модуль — основной строительный блок SLM-архитектуры. Это папка с публичным API (`index.ts`) и опциональными сегментами: компонентами, стилями, типами, хуками, сторами, сервисами и вложенными модулями.
|
Архитектурное определение модуля описано в разделе [Архитектура → Модули](../basics/architecture/modules.md). Список сегментов описан в разделе [Архитектура → Сегменты](../basics/architecture/segments.md).
|
||||||
|
|
||||||
Если UI-сущность остаётся одним `.tsx` файлом и использует ресурсы родительского модуля — это [компонент](./component.md), а не модуль. Связанные файлы в `styles/` и `types/` родителя не создают новую модульную границу.
|
Эта страница показывает прикладное оформление трёх типов модулей: UI, бизнес и инфраструктурный.
|
||||||
|
|
||||||
Архитектурное определение: [Модули SLM](../basics/architecture/reference/modules.md). Список сегментов: [Сегменты SLM](../basics/architecture/reference/segments.md).
|
## Создание
|
||||||
|
|
||||||
## Когда нужен модуль
|
1. Проверьте, что в проекте есть нужный шаблон в `.templates/`.
|
||||||
|
2. Если шаблона нет — создайте его по разделу [Создание шаблонов](./templates/templates-create.md).
|
||||||
|
3. Сгенерируйте модуль через [VS Code или CLI](./templates/templates-usage.md).
|
||||||
|
|
||||||
Создавайте модуль, если сущности нужны:
|
## Типы модулей
|
||||||
|
|
||||||
- публичный API;
|
Архитектура определяет три типа модулей ([Типы модулей](../basics/architecture/modules.md#типы-модулей)):
|
||||||
- хуки, сторы, сервисы, мапперы или утилиты;
|
|
||||||
- вложенные части;
|
|
||||||
- переиспользование вне родительского модуля;
|
|
||||||
- самостоятельная ответственность на слое.
|
|
||||||
|
|
||||||
Если понадобилась папка вокруг компонента — это сигнал, что нужен модуль.
|
| Тип | Обязательный файл | Описание |
|
||||||
|
|---|---|---|
|
||||||
|
| UI-модуль | `{name}.tsx` | Модуль, выросший из компонента |
|
||||||
|
| Бизнес-модуль | `{name}.factory.ts` | Модуль вокруг публичного runtime API |
|
||||||
|
| Инфраструктурный модуль | нет | Модуль вокруг технического сервиса |
|
||||||
|
|
||||||
## Где размещать
|
## UI-модуль
|
||||||
|
|
||||||
Модуль размещается на самом низком уровне использования.
|
UI-модуль — это компонент, который перерос ограничения компонента: получил собственные хуки, вложенные модули в `parts/`, сценарную логику или публичный API. Внутренняя структура та же, что у компонента: корневой `.tsx`, типы, стили, `ui/`. Но без ограничений компонента.
|
||||||
|
|
||||||
- Нужен только одному модулю — размещается в `parts/` родителя.
|
Подробное оформление компонентов внутри `ui/` описано в разделе [Компонент](./component.md).
|
||||||
- Нужен одной странице — размещается в `screens/{name}/parts/`.
|
|
||||||
- Нужен одному layout — размещается в `layouts/{name}/parts/`.
|
|
||||||
- Переиспользуется между страницами или layout — поднимается в `widgets/`.
|
|
||||||
- Представляет бизнес-домен — размещается в `business/`.
|
|
||||||
- Является UI-китом — размещается в `ui/`.
|
|
||||||
|
|
||||||
`app/` не содержит модулей. Это слой файлового роутинга и инициализации.
|
## Бизнес-модуль
|
||||||
|
|
||||||
## Структура
|
Бизнес-модуль строится вокруг публичного runtime API. Ключевой файл — фабрика (`{name}.factory.ts`), которая возвращает всё, что нужно внешнему коду в runtime.
|
||||||
|
|
||||||
Минимальный UI-модуль:
|
Архитектурное описание фабрики: [Архитектура → Фабрика](../basics/architecture/modules.md#фабрика).
|
||||||
|
|
||||||
|
### Структура
|
||||||
|
|
||||||
```text
|
```text
|
||||||
user/
|
business/customer/
|
||||||
├── user.tsx
|
├── customer.factory.ts
|
||||||
└── index.ts
|
├── index.ts
|
||||||
|
└── types/
|
||||||
|
├── customer.type.ts
|
||||||
|
├── customer-api.type.ts
|
||||||
|
├── customer-deps.type.ts
|
||||||
|
└── customer-factory.type.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Модуль расширяется сегментами только при реальной потребности:
|
### Типы
|
||||||
|
|
||||||
```text
|
`business/customer/types/customer-api.type.ts`
|
||||||
user/
|
|
||||||
├── ui/
|
|
||||||
│ └── user-avatar.tsx
|
|
||||||
├── parts/
|
|
||||||
│ └── user-posts/
|
|
||||||
│ ├── user-posts.tsx
|
|
||||||
│ └── index.ts
|
|
||||||
├── styles/
|
|
||||||
│ ├── user.module.css
|
|
||||||
│ └── user-avatar.module.css
|
|
||||||
├── types/
|
|
||||||
│ ├── user.type.ts
|
|
||||||
│ └── user-avatar.type.ts
|
|
||||||
├── user.tsx
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Корневой компонент опционален. Business- и infrastructure-модули могут состоять только из хуков, сервисов, типов и публичного API.
|
|
||||||
|
|
||||||
## `ui/` и `parts/`
|
|
||||||
|
|
||||||
`ui/` содержит только компоненты: отдельные `.tsx` файлы без собственных сегментов.
|
|
||||||
|
|
||||||
`parts/` содержит только модули: каждая запись внутри `parts/` — папка с собственным `index.ts`. Отдельные `.tsx`, стили, хуки или произвольные файлы в `parts/` не кладутся.
|
|
||||||
|
|
||||||
```text
|
|
||||||
user/
|
|
||||||
├── ui/
|
|
||||||
│ └── user-avatar.tsx # компонент
|
|
||||||
└── parts/
|
|
||||||
└── user-posts/ # модуль
|
|
||||||
├── styles/
|
|
||||||
│ └── user-posts.module.css
|
|
||||||
├── user-posts.tsx
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
Если компоненту в `ui/` понадобились стили или типы, они добавляются в `styles/` и `types/` родительского модуля. Если компоненту нужны собственные хуки, вложенные части или публичная граница — он переносится в `parts/` как модуль.
|
|
||||||
|
|
||||||
## Публичный API
|
|
||||||
|
|
||||||
`index.ts` — единственная точка входа в модуль. Внешние импорты внутренних файлов запрещены.
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// user/index.ts
|
export type CustomerApi = {
|
||||||
export { User } from './user'
|
useCustomer: () => Customer
|
||||||
export type { UserProps } from './types/user.type'
|
CustomerCard: (props: CustomerCardProps) => ReactNode
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`business/order/types/order-deps.type.ts`
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Плохо: импорт в обход публичного API.
|
export type OrderDeps = {
|
||||||
import { UserPosts } from 'screens/user/parts/user-posts/user-posts'
|
customer: Pick<CustomerApi, 'useCustomer'>
|
||||||
|
}
|
||||||
// Хорошо: импорт через публичный API родительского модуля.
|
|
||||||
import { User } from 'screens/user'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Вложенный модуль имеет свой `index.ts`, но наружу родителя экспортируется только при необходимости.
|
`business/order/types/order-factory.type.ts`
|
||||||
|
|
||||||
## Именование
|
```ts
|
||||||
|
export type OrderFactory = (deps: OrderDeps) => OrderApi
|
||||||
Базовые правила описаны в разделе [Именование](../basics/naming.md).
|
|
||||||
|
|
||||||
- Папка модуля — `kebab-case`: `user-posts/`.
|
|
||||||
- Файл корневого компонента повторяет имя папки: `user-posts/user-posts.tsx`.
|
|
||||||
- Корневые модули слоёв наследуют роль слоя в имени файла: `screens/profile/profile.screen.tsx`, `layouts/main/main.layout.tsx`.
|
|
||||||
- Корневой компонент именуется в `PascalCase`: `UserPosts`.
|
|
||||||
- Если имя без контекста слишком общее, добавляется префикс родителя или роль слоя: `ProfileUserPosts`, `ProfileScreen`, `MainLayout`.
|
|
||||||
|
|
||||||
## Примеры
|
|
||||||
|
|
||||||
### Screen-модуль
|
|
||||||
|
|
||||||
```text
|
|
||||||
screens/profile/
|
|
||||||
├── ui/
|
|
||||||
│ └── profile-heading.tsx
|
|
||||||
├── parts/
|
|
||||||
│ └── activity-feed/
|
|
||||||
│ ├── styles/
|
|
||||||
│ │ └── activity-feed.module.css
|
|
||||||
│ ├── activity-feed.tsx
|
|
||||||
│ └── index.ts
|
|
||||||
├── styles/
|
|
||||||
│ ├── profile.module.css
|
|
||||||
│ └── profile-heading.module.css
|
|
||||||
├── types/
|
|
||||||
│ ├── profile.type.ts
|
|
||||||
│ └── profile-heading.type.ts
|
|
||||||
├── profile.screen.tsx
|
|
||||||
└── index.ts
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Business-модуль без корневого компонента
|
### Фабрика без зависимостей
|
||||||
|
|
||||||
|
`business/customer/customer.factory.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { CustomerFactory } from './types/customer-factory.type'
|
||||||
|
|
||||||
|
export const customerFactory: CustomerFactory = () => {
|
||||||
|
return {
|
||||||
|
useCustomer,
|
||||||
|
CustomerCard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Фабрика с зависимостями
|
||||||
|
|
||||||
|
`business/order/order.factory.ts`
|
||||||
|
|
||||||
|
```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} />
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Инфраструктурный модуль
|
||||||
|
|
||||||
|
Инфраструктурный модуль строится вокруг технического сервиса или интеграции. Его структура определяется природой сервиса — фиксированного корневого файла нет.
|
||||||
|
|
||||||
|
Архитектурное описание: [Архитектура → Типы модулей → Инфраструктурный модуль](../basics/architecture/modules.md#инфраструктурный-модуль).
|
||||||
|
|
||||||
|
Пример модуля темы:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
business/auth/
|
theme/
|
||||||
|
├── index.ts
|
||||||
|
├── config/
|
||||||
├── hooks/
|
├── hooks/
|
||||||
│ └── use-auth.hook.ts
|
├── styles/
|
||||||
├── services/
|
└── ui/
|
||||||
│ └── auth.service.ts
|
```
|
||||||
├── stores/
|
|
||||||
│ └── auth.store.ts
|
Пример модуля API-клиента:
|
||||||
|
|
||||||
|
```text
|
||||||
|
backend-api/
|
||||||
|
├── backend-api.client.ts
|
||||||
|
├── config/
|
||||||
├── types/
|
├── types/
|
||||||
│ └── auth.type.ts
|
|
||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ description: Как работать со страницами и другими
|
|||||||
|
|
||||||
Файлы роутинга не реализуют интерфейс. Они описывают маршрут: читают параметры, получают данные первого рендера, подготавливают кеш или состояние и передают результат в screen.
|
Файлы роутинга не реализуют интерфейс. Они описывают маршрут: читают параметры, получают данные первого рендера, подготавливают кеш или состояние и передают результат в screen.
|
||||||
|
|
||||||
Границы слоя описаны в [Архитектура → Слои → App](../basics/architecture/reference/layers.md#слой-app).
|
Границы слоя описаны в [Архитектура → Слои → App](../basics/architecture/layers.md#слой-app).
|
||||||
|
|
||||||
## Граница ответственности
|
## Граница ответственности
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ description: Как работать со страницами и другими
|
|||||||
| Прогрев SWR-кеша, начальное состояние, подключение провайдеров | `src/app/**`, только через готовые обёртки из нижних слоёв |
|
| Прогрев SWR-кеша, начальное состояние, подключение провайдеров | `src/app/**`, только через готовые обёртки из нижних слоёв |
|
||||||
| UI страницы | `screens/` |
|
| UI страницы | `screens/` |
|
||||||
| Каркас страницы: header, footer, sidebar | `layouts/` |
|
| Каркас страницы: header, footer, sidebar | `layouts/` |
|
||||||
| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infrastructure/`, `shared/`) |
|
| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infra/`, `shared/`) |
|
||||||
| CSS Modules и стили компонентов | рядом с компонентами, не в `src/app/**` |
|
| CSS Modules и стили компонентов | рядом с компонентами, не в `src/app/**` |
|
||||||
|
|
||||||
## Что можно делать в `page.tsx`
|
## Что можно делать в `page.tsx`
|
||||||
@@ -79,7 +79,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { userApi } from 'infrastructure/backend-api'
|
import { userApi } from 'infra/backend-api'
|
||||||
import { UserScreen } from 'screens/user'
|
import { UserScreen } from 'screens/user'
|
||||||
|
|
||||||
type UserPageProps = {
|
type UserPageProps = {
|
||||||
@@ -109,7 +109,7 @@ import {
|
|||||||
backendApi,
|
backendApi,
|
||||||
getCurrentUserKey,
|
getCurrentUserKey,
|
||||||
getPostListKey,
|
getPostListKey,
|
||||||
} from 'infrastructure/backend-api'
|
} from 'infra/backend-api'
|
||||||
|
|
||||||
type FeedLayoutProps = {
|
type FeedLayoutProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ src/
|
|||||||
├── screens/ # Контент конкретной страницы
|
├── screens/ # Контент конкретной страницы
|
||||||
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
|
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
|
||||||
├── business/ # Бизнес-домены (auth, catalog, orders)
|
├── business/ # Бизнес-домены (auth, catalog, orders)
|
||||||
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры)
|
├── infra/ # Техсервисы (theme, i18n, API-адаптеры)
|
||||||
├── ui/ # UI-кит без бизнес-логики
|
├── ui/ # UI-кит без бизнес-логики
|
||||||
└── shared/ # Общие ресурсы (утилиты, типы, стили)
|
└── shared/ # Общие ресурсы (утилиты, типы, стили)
|
||||||
```
|
```
|
||||||
@@ -58,7 +58,7 @@ src/
|
|||||||
Точка входа приложения и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
|
Точка входа приложения и файловый роутинг Next.js (`layout.tsx`, `page.tsx`, route-сегменты).
|
||||||
`app/` подключает готовую инициализацию из нижних слоёв, но не реализует провайдеры, стили, UI-компоненты, хуки, сторы или сервисы.
|
`app/` подключает готовую инициализацию из нижних слоёв, но не реализует провайдеры, стили, UI-компоненты, хуки, сторы или сервисы.
|
||||||
|
|
||||||
Подробнее о границах слоя: [Архитектура → Слои → App](../basics/architecture/reference/layers.md#слой-app).
|
Подробнее о границах слоя: [Архитектура → Слои → App](../basics/architecture/layers.md#слой-app).
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/app/
|
src/app/
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ keywords: [шаблоны, templates, .templates, syntax, переменные,
|
|||||||
│ ├── styles/
|
│ ├── styles/
|
||||||
│ │ └── {{name.kebabCase}}.module.css
|
│ │ └── {{name.kebabCase}}.module.css
|
||||||
│ ├── types/
|
│ ├── types/
|
||||||
│ │ └── {{name.kebabCase}}.type.ts
|
│ │ └── {{name.kebabCase}}-props.type.ts
|
||||||
│ ├── {{name.kebabCase}}.tsx
|
│ ├── {{name.kebabCase}}.tsx
|
||||||
│ └── index.ts
|
│ └── index.ts
|
||||||
└── store/ # шаблон Zustand стора
|
└── store/ # шаблон Zustand стора
|
||||||
@@ -32,6 +32,12 @@ keywords: [шаблоны, templates, .templates, syntax, переменные,
|
|||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Обязательный шаблон компонента
|
||||||
|
|
||||||
|
Перед созданием компонентов в проекте должен существовать шаблон `.templates/component`.
|
||||||
|
|
||||||
|
Если шаблона нет, компонент не создаётся вручную. Сначала создаётся шаблон компонента, затем компонент генерируется через [VS Code или CLI](./templates-usage.md).
|
||||||
|
|
||||||
## Синтаксис шаблонов
|
## Синтаксис шаблонов
|
||||||
|
|
||||||
### Переменные
|
### Переменные
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ keywords: [шаблоны, templates, generate, VS Code, CLI, gromlab/create, np
|
|||||||
|
|
||||||
Генерация файлов из шаблонов через VS Code плагин и CLI.
|
Генерация файлов из шаблонов через VS Code плагин и CLI.
|
||||||
|
|
||||||
|
::: danger Ручное создание запрещено
|
||||||
|
Файлы, для которых есть шаблоны в `.templates/`, создаются только генератором. Ручное создание компонента, модуля, стора или другого шаблонного блока запрещено.
|
||||||
|
|
||||||
|
Если нужного шаблона нет, сначала создайте шаблон в `.templates/`, затем сгенерируйте код на его основе.
|
||||||
|
:::
|
||||||
|
|
||||||
## Через VS Code
|
## Через VS Code
|
||||||
|
|
||||||
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
|
Template File Generator | gromlab ([Marketplace](https://marketplace.visualstudio.com/items?itemName=gromlab.vscode-templateFileGenerator), [Open VSX](https://open-vsx.org/extension/gromlab/vscode-templateFileGenerator)) — расширение для генерации файлов и папок из шаблонов через интерфейс редактора.
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
---
|
---
|
||||||
title: SLM Design
|
title: SLM Design
|
||||||
description: "Архитектурный подход проекта: что такое SLM и как он устроен."
|
description: Назначение архитектуры, ключевые принципы и карта разделов документации
|
||||||
---
|
---
|
||||||
|
|
||||||
# SLM Design
|
# SLM Design
|
||||||
|
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||||||
|
|
||||||
Архитектурный подход проекта: что такое SLM и как он устроен.
|
## Разделы спецификации
|
||||||
|
|
||||||
|
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
||||||
|
|
||||||
|
- [Слои](./layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
||||||
|
- [Модули](./modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
||||||
|
- [Сегменты](./segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
||||||
|
|
||||||
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
|
||||||
|
|
||||||
## Преимущества
|
## Преимущества
|
||||||
|
|
||||||
@@ -19,7 +28,7 @@ Cross-domain зависимости в бизнес-слое реализуют
|
|||||||
|
|
||||||
### Разделение ответственности без перегрузки слоёв
|
### Разделение ответственности без перегрузки слоёв
|
||||||
|
|
||||||
Сервисы приложения (`infrastructure/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
|
Сервисы приложения (`infra/`), UI-кит (`ui/`) и общие ресурсы (`shared/`) — три разных слоя с разной природой. Ни один слой не превращается в свалку разнородного кода.
|
||||||
|
|
||||||
### Горизонтальная инкапсуляция
|
### Горизонтальная инкапсуляция
|
||||||
|
|
||||||
@@ -73,7 +82,7 @@ src/
|
|||||||
│ ├── orders/
|
│ ├── orders/
|
||||||
│ └── chat/
|
│ └── chat/
|
||||||
│
|
│
|
||||||
├── infrastructure/
|
├── infra/
|
||||||
│ ├── theme/
|
│ ├── theme/
|
||||||
│ ├── i18n/
|
│ ├── i18n/
|
||||||
│ ├── backend-api/
|
│ ├── backend-api/
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Слои SLM
|
title: Слои
|
||||||
description: Из каких слоёв состоит SLM-архитектура и как они связаны.
|
description: Иерархия слоёв от app до shared, правила зависимостей и зона ответственности каждого слоя
|
||||||
---
|
---
|
||||||
|
|
||||||
# Слои SLM
|
# Слои
|
||||||
|
|
||||||
Из каких слоёв состоит SLM-архитектура и как они связаны.
|
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
||||||
|
|
||||||
## Определение
|
## Определение
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ description: Из каких слоёв состоит SLM-архитектур
|
|||||||
| Группа | Слои | Описание |
|
| Группа | Слои | Описание |
|
||||||
|--------|------|----------|
|
|--------|------|----------|
|
||||||
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
|
| Композиция | `app`, `layouts`, `screens`, `widgets` | Собирают интерфейс из готовых блоков: маршруты, каркасы, страницы |
|
||||||
| Ядро | `business`, `infrastructure`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
|
| Ядро | `business`, `infra`, `ui` | Реализация продукта: бизнес-домены, техсервисы, UI-кит |
|
||||||
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
|
| Фундамент | `shared` | Общие ресурсы: утилиты, хелперы, стили, конфиги |
|
||||||
|
|
||||||
## Направление зависимостей
|
## Направление зависимостей
|
||||||
@@ -26,12 +26,12 @@ description: Из каких слоёв состоит SLM-архитектур
|
|||||||
Любой импорт между модулями — только через публичный API.
|
Любой импорт между модулями — только через публичный API.
|
||||||
|
|
||||||
```
|
```
|
||||||
app → [ layouts | screens ] → widgets → business → infrastructure → ui → shared
|
app → [ layouts | screens ] → widgets → business → infra → ui → shared
|
||||||
```
|
```
|
||||||
|
|
||||||
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
|
- `layouts` и `screens` — параллельные слои, не импортируют друг друга
|
||||||
- Модули одного слоя в группе «Композиция» изолированы друг от друга
|
- Модули одного слоя в группе «Композиция» изолированы друг от друга
|
||||||
- Модули одного слоя `infrastructure` и `ui` могут импортировать друг друга через публичный API
|
- Модули одного слоя `infra` и `ui` могут импортировать друг друга через публичный API
|
||||||
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
|
- Модули `business` — cross-domain зависимости по коду через фабрику, `import type` напрямую
|
||||||
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
|
- Импорт типов (`import type`) в «Ядре» разрешён в обоих направлениях
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ src/widgets/
|
|||||||
|
|
||||||
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
|
Бизнес-домены приложения: auth, catalog, orders, checkout, chat. Каждый домен — отдельный модуль со своими типами, логикой, UI и сервисами.
|
||||||
|
|
||||||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`. Cross-domain зависимости по коду реализуются через фабрику. `import type` между доменами разрешён напрямую.
|
Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`. Каждый бизнес-модуль создаёт публичный runtime API через фабрику в корне. Cross-domain зависимости: runtime — через аргументы фабрики, типы — напрямую через `import type`.
|
||||||
|
|
||||||
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
|
Business объединяет то, что в FSD разделено на `features` и `entities`: пользовательские сценарии и бизнес-сущности живут вместе, внутри одного домена. Внутри домена сегменты разделяют ответственность: `types/` — доменная модель, `hooks/` и `services/` — сценарии и логика, `mappers/` — трансформация данных, `parts/` — составные блоки.
|
||||||
|
|
||||||
@@ -160,19 +160,20 @@ src/business/
|
|||||||
|
|
||||||
- Один модуль = один бизнес-домен
|
- Один модуль = один бизнес-домен
|
||||||
- Циклические зависимости между доменами запрещены
|
- Циклические зависимости между доменами запрещены
|
||||||
- Импорт кода между доменами — через фабрику. `import type` — напрямую
|
- Публичный runtime API — через фабрику в корне модуля (`{name}.factory.ts`). `index.ts` экспортирует только фабрику и type-only экспорты
|
||||||
|
- Импорт runtime-кода между доменами — через фабрику. `import type` — напрямую
|
||||||
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
|
- Доменные типы (`User`, `Product`) живут здесь, не в `shared/`
|
||||||
|
|
||||||
## Слой Infrastructure
|
## Слой infra
|
||||||
|
|
||||||
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
|
Техсервисы приложения: theme, i18n, API-адаптеры, logger, realtime. Каждый сервис — отдельный модуль.
|
||||||
|
|
||||||
Слой входит в группу «Ядро». Импортирует `infrastructure/`, `ui/`, `shared/`.
|
Слой входит в группу «Ядро». Импортирует `infra/`, `ui/`, `shared/`.
|
||||||
|
|
||||||
Отличие от `shared/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
Отличие от `shared/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/
|
src/infra/
|
||||||
├── theme/
|
├── theme/
|
||||||
├── i18n/
|
├── i18n/
|
||||||
├── backend-api/
|
├── backend-api/
|
||||||
@@ -185,7 +186,7 @@ src/infrastructure/
|
|||||||
### Требования
|
### Требования
|
||||||
|
|
||||||
- Один модуль = один техсервис
|
- Один модуль = один техсервис
|
||||||
- Импортирует `infrastructure/`, `ui/`, `shared/`
|
- Импортирует `infra/`, `ui/`, `shared/`
|
||||||
|
|
||||||
## Слой UI
|
## Слой UI
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@ src/ui/
|
|||||||
|
|
||||||
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
|
Слой входит в группу «Фундамент» — ни о ком не знает, никого не импортирует.
|
||||||
|
|
||||||
Отличие от `infrastructure/`: infrastructure — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
Отличие от `infra/`: infra — инфраструктура приложения (сервисы, темы, адаптеры к API), `shared/` — общие ресурсы (утилиты, хелперы, стили, конфиги).
|
||||||
|
|
||||||
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
|
Отличие от `ui/`: UI-компоненты (button, carousel, modal) живут в слое `ui/`, а не здесь.
|
||||||
|
|
||||||
289
ai/nextjs-style-guide/basics/architecture/modules.md
Normal file
289
ai/nextjs-style-guide/basics/architecture/modules.md
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Подробное описание сегментов — в разделе [Сегменты](./segments.md).
|
||||||
|
|
||||||
|
## Публичный 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/`
|
||||||
|
|
||||||
|
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
---
|
|
||||||
title: Модули SLM
|
|
||||||
description: Что такое модуль в SLM-архитектуре и как он устроен.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Модули SLM
|
|
||||||
|
|
||||||
Что такое модуль в SLM-архитектуре и как он устроен.
|
|
||||||
|
|
||||||
## Определение
|
|
||||||
|
|
||||||
**Модуль — универсальный строительный блок архитектуры. Живёт на слое и содержит всё необходимое для своей работы: компоненты, хуки, сторы, сервисы, типы, стили. Набор содержимого не фиксирован — включаются только нужные части.**
|
|
||||||
|
|
||||||
## Модуль vs компонент
|
|
||||||
|
|
||||||
**Компонент** — один `.tsx` файл. Не имеет своих сегментов, использует сегменты родительского модуля. Живёт в корне или `ui/` сегменте модуля.
|
|
||||||
|
|
||||||
**Модуль** — папка, которая может содержать корневой компонент, сегменты (`hooks/`, `types/`, `styles/`, `ui/`, `parts/` и т.д.) и публичный API (`index.ts`).
|
|
||||||
|
|
||||||
```text
|
|
||||||
auth/
|
|
||||||
├── ui/
|
|
||||||
│ ├── auth-guard.tsx
|
|
||||||
│ └── logout-button.tsx
|
|
||||||
├── parts/
|
|
||||||
│ ├── login-form/
|
|
||||||
│ ├── registration-form/
|
|
||||||
│ └── restore-form/
|
|
||||||
├── hooks/
|
|
||||||
├── stores/
|
|
||||||
├── types/
|
|
||||||
├── auth.tsx # корневой компонент (опционален)
|
|
||||||
└── index.ts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Структура
|
|
||||||
|
|
||||||
Модуль состоит из сегментов. Ни один сегмент не обязателен — модуль может состоять даже из одного `index.ts` с реэкспортом типов.
|
|
||||||
|
|
||||||
```text
|
|
||||||
{module-name}/
|
|
||||||
├── {module-name}.tsx # корневой компонент (опционален)
|
|
||||||
├── ui/ # компоненты модуля (только .tsx)
|
|
||||||
├── parts/ # вложенные модули (со своими сегментами)
|
|
||||||
├── hooks/ # хуки
|
|
||||||
├── stores/ # сторы состояния
|
|
||||||
├── services/ # внешние источники данных
|
|
||||||
├── mappers/ # трансформация данных между форматами
|
|
||||||
├── types/ # типы
|
|
||||||
├── styles/ # стили
|
|
||||||
├── lib/ # утилиты модуля
|
|
||||||
├── config/ # константы
|
|
||||||
└── index.ts # публичный API
|
|
||||||
```
|
|
||||||
|
|
||||||
Подробное описание каждого сегмента — в разделе [Сегменты](./segments.md).
|
|
||||||
|
|
||||||
## Публичный API
|
|
||||||
|
|
||||||
Модуль экспортирует наружу только то, что нужно другим. Всё остальное — внутреннее.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/auth/index.ts
|
|
||||||
export type { User, Session } from './types/user.types'
|
|
||||||
export { useAuth } from './hooks/use-auth.hook'
|
|
||||||
export { AuthGuard } from './ui/auth-guard'
|
|
||||||
```
|
|
||||||
|
|
||||||
Импорт в обход `index.ts` запрещён:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// Плохо
|
|
||||||
import { validateToken } from '@/business/auth/lib/tokens'
|
|
||||||
|
|
||||||
// Хорошо
|
|
||||||
import { useAuth } from '@/business/auth'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Фабрика
|
|
||||||
|
|
||||||
Если модуль зависит от кода другого бизнес-домена — он экспортирует фабрику. Фабрика декларирует необходимые зависимости и возвращает API модуля. Точка использования (screen, widget, layout) предоставляет зависимости при вызове.
|
|
||||||
|
|
||||||
Модуль без cross-domain зависимостей экспортирует API напрямую. Типы всегда экспортируются напрямую — `import type` не является runtime-зависимостью.
|
|
||||||
|
|
||||||
### Модуль без зависимостей — прямой экспорт:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/auth/index.ts
|
|
||||||
export { useAuth } from './hooks/use-auth'
|
|
||||||
export { useCurrentUser } from './hooks/use-current-user'
|
|
||||||
export type { User, Session } from './types'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Модуль с зависимостями — фабрика:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/chat/types/deps.ts
|
|
||||||
import type { User } from '@/business/auth'
|
|
||||||
|
|
||||||
export interface ChatDeps {
|
|
||||||
useCurrentUser: () => User | null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// business/chat/index.ts
|
|
||||||
import type { ChatDeps } from './types/deps'
|
|
||||||
|
|
||||||
export function chatFactory(deps: ChatDeps) {
|
|
||||||
return {
|
|
||||||
useMessages: (roomId: string) => {
|
|
||||||
const user = deps.useCurrentUser()
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
useSendMessage: (roomId: string) => {
|
|
||||||
const user = deps.useCurrentUser()
|
|
||||||
return (text: string) => { /* ... */ }
|
|
||||||
},
|
|
||||||
useChatRooms: () => {
|
|
||||||
const user = deps.useCurrentUser()
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
ChatBadge: ({ count }: { count: number }) => { /* ... */ },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { Message, ChatRoom } from './types'
|
|
||||||
export type { ChatDeps } from './types/deps'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Использование на странице:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// screens/support/support.tsx
|
|
||||||
import { useCurrentUser } from '@/business/auth'
|
|
||||||
import { chatFactory } from '@/business/chat'
|
|
||||||
|
|
||||||
const chat = chatFactory({ useCurrentUser })
|
|
||||||
|
|
||||||
export function SupportScreen() {
|
|
||||||
const { useMessages, useSendMessage, ChatBadge } = chat
|
|
||||||
const messages = useMessages('support')
|
|
||||||
const sendMessage = useSendMessage('support')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<ChatBadge count={messages.length} />
|
|
||||||
{messages.map(m => <MessageBubble key={m.id} {...m} />)}
|
|
||||||
<MessageInput onSend={sendMessage} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Жизненный цикл
|
|
||||||
|
|
||||||
Модуль рождается на самом низком уровне использования и поднимается выше только при реальной потребности.
|
|
||||||
|
|
||||||
- Нужен на одной странице → `screens/{name}/parts/`
|
|
||||||
- Появился в 2+ местах → поднимается по природе:
|
|
||||||
- абстрактный UI → `ui/`
|
|
||||||
- блок с данными/логикой → `widgets/`
|
|
||||||
- представление бизнес-домена → `business/{area}/parts/`
|
|
||||||
|
|
||||||
Подъём — обычный рефакторинг в рамках задачи, а не отдельная активность.
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
title: Сегменты SLM
|
title: Сегменты
|
||||||
description: Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
|
description: Сегменты внутри модуля (ui/, model/, lib/ и др.), назначение и правила размещения файлов
|
||||||
---
|
---
|
||||||
|
|
||||||
# Сегменты SLM
|
# Сегменты
|
||||||
|
|
||||||
Что такое сегмент модуля в SLM-архитектуре и какие они бывают.
|
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
||||||
|
|
||||||
## Определение
|
## Определение
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ description: Что такое сегмент модуля в SLM-архитек
|
|||||||
|
|
||||||
| Сегмент | Содержимое |
|
| Сегмент | Содержимое |
|
||||||
|---------|------------|
|
|---------|------------|
|
||||||
| `ui/` | Вспомогательные компоненты модуля — только `.tsx` файлы |
|
| `ui/` | Презентационные компоненты родительского модуля |
|
||||||
| `parts/` | Вложенные модули со своими сегментами |
|
| `parts/` | Вложенные модули со своими сегментами |
|
||||||
| `hooks/` | React-хуки |
|
| `hooks/` | React-хуки |
|
||||||
| `stores/` | Сторы состояния |
|
| `stores/` | Сторы состояния |
|
||||||
@@ -28,22 +28,44 @@ description: Что такое сегмент модуля в SLM-архитек
|
|||||||
|
|
||||||
## Сегмент ui/
|
## Сегмент ui/
|
||||||
|
|
||||||
Вспомогательные компоненты модуля. Содержит только `.tsx` файлы — без своих сегментов, стилей, типов, хуков и публичного API. Использует сегменты родительского модуля.
|
Презентационные компоненты родительского модуля. `ui/` содержит только компоненты, которые отвечают за отображение части интерфейса и не выходят за границы своего модуля.
|
||||||
|
|
||||||
Корневой компонент модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
Компонент в `ui/`:
|
||||||
|
|
||||||
|
- Находится в собственной папке.
|
||||||
|
- Может содержать только `{name}.tsx`, `index.ts`, `styles/`, `types/`.
|
||||||
|
- Не содержит любые компоненты.
|
||||||
|
- Не содержит любые модули.
|
||||||
|
- Не импортирует код проекта за пределами родительского модуля.
|
||||||
|
- Не делает внешние запросы.
|
||||||
|
- Не вызывает сценарные хуки.
|
||||||
|
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
||||||
|
- Не содержит бизнес-логику или сценарную логику.
|
||||||
|
|
||||||
|
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](./modules.md#компонент).
|
||||||
|
|
||||||
|
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
user/
|
user/
|
||||||
├── ui/
|
├── ui/
|
||||||
│ ├── user-avatar.tsx
|
│ ├── user-avatar/
|
||||||
│ └── user-status.tsx
|
│ │ ├── user-avatar.tsx
|
||||||
|
│ │ ├── styles/
|
||||||
|
│ │ │ └── user-avatar.module.css
|
||||||
|
│ │ ├── types/
|
||||||
|
│ │ │ └── user-avatar-props.type.ts
|
||||||
|
│ │ └── index.ts
|
||||||
|
│ └── user-status/
|
||||||
|
│ ├── user-status.tsx
|
||||||
|
│ └── index.ts
|
||||||
├── types/
|
├── types/
|
||||||
├── hooks/
|
├── hooks/
|
||||||
├── user.tsx
|
├── user.tsx
|
||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Если компоненту нужны собственные сегменты — это уже не `ui/`, а `parts/`.
|
Если UI-сущности нужна внутренняя декомпозиция, сценарная логика, получение данных или собственные архитектурные зависимости — это уже не компонент в `ui/`, а модуль в `parts/`.
|
||||||
|
|
||||||
## Сегмент parts/
|
## Сегмент parts/
|
||||||
|
|
||||||
@@ -68,7 +90,7 @@ home/
|
|||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — вспомогательный компонент, один `.tsx` файл.
|
Отличие от `ui/`: элемент `parts/` — модульная папка со своими сегментами. Элемент `ui/` — компонент родительского модуля без собственной архитектурной ответственности.
|
||||||
|
|
||||||
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
|
Вложенность `parts/` инкапсулирует область разработки горизонтально: каждый разработчик работает в своём `parts/`-модуле, не затрагивая чужие. Это снижает конфликты при параллельной разработке.
|
||||||
|
|
||||||
@@ -9,11 +9,16 @@ description: Как типизируется код в проекте.
|
|||||||
|
|
||||||
## Общие правила
|
## Общие правила
|
||||||
|
|
||||||
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
|
- Указывать типы для параметров компонентов и параметров функций.
|
||||||
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
|
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
|
||||||
- Избегать `any` и `unknown` без необходимости.
|
- Избегать `any` и `unknown` без необходимости.
|
||||||
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
|
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
|
||||||
|
|
||||||
|
## React-компоненты
|
||||||
|
|
||||||
|
- Пропсы компонента типизировать через отдельный `Props`.
|
||||||
|
- Возвращаемый тип компонента не указывать: TypeScript корректно выводит JSX-результат, а явный `ReactElement` сужает допустимые варианты возврата.
|
||||||
|
|
||||||
## Функции
|
## Функции
|
||||||
|
|
||||||
- Для публичных функций указывать возвращаемый тип.
|
- Для публичных функций указывать возвращаемый тип.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ keywords: [создать проект, новый проект, с нуля, in
|
|||||||
|
|
||||||
## Канон раскладки
|
## Канон раскладки
|
||||||
|
|
||||||
В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](../applied/project-structure.md), [Архитектура](../basics/architecture/index.md)).
|
В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](../applied/project-structure.md), [Архитектура](../basics/architecture/index.md)).
|
||||||
|
|
||||||
В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`.
|
В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`.
|
||||||
|
|
||||||
@@ -85,6 +85,6 @@ CSS-процессор поверх базовых стилей: `@custom-media`
|
|||||||
|
|
||||||
- **Порядок шагов фиксирован.** Перестановка ломает зависимости (PostCSS требует базовых стилей, VS Code — установленного Biome).
|
- **Порядок шагов фиксирован.** Перестановка ломает зависимости (PostCSS требует базовых стилей, VS Code — установленного Biome).
|
||||||
- **Между шагами обязательна проверка** из соответствующего раздела. Не переходить дальше, пока чеклист текущего шага не пройден.
|
- **Между шагами обязательна проверка** из соответствующего раздела. Не переходить дальше, пока чеклист текущего шага не пройден.
|
||||||
- **Слои `src/`** (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) не создавать авансом. Появляются по мере первого модуля. Исключения — `src/app/` (создаётся `create-next-app`), `src/shared/styles/` (шаг 1) и `src/shared/sprites/icons/` (шаг 6).
|
- **Слои `src/`** (`layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`) не создавать авансом. Появляются по мере первого модуля. Исключения — `src/app/` (создаётся `create-next-app`), `src/shared/styles/` (шаг 1) и `src/shared/sprites/icons/` (шаг 6).
|
||||||
- **Посторонние каталоги в `src/`** (`assets/`, `utils/`, `lib/`, `components/` и т.п.) — запрещены.
|
- **Посторонние каталоги в `src/`** (`assets/`, `utils/`, `lib/`, `components/` и т.п.) — запрещены.
|
||||||
- **Подмножество шагов допустимо.** Можно ставить только Next.js и часть инструментов; полный набор — это эталон, а не обязательство.
|
- **Подмножество шагов допустимо.** Можно ставить только Next.js и часть инструментов; полный набор — это эталон, а не обязательство.
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
mkdir -p src/shared/styles
|
mkdir -p src/shared/styles
|
||||||
```
|
```
|
||||||
|
|
||||||
Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах.
|
Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах.
|
||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Источники данных
|
title: Источники данных
|
||||||
description: Какие источники данных используются в проекте и как с ними работать.
|
description: Какие источники данных используются в проекте и как с ними работать.
|
||||||
keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, введение, карта раздела]
|
keywords: [данные, api, rest, realtime, клиент, swr, infra, введение, карта раздела]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Источники данных
|
# Источники данных
|
||||||
@@ -10,7 +10,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure,
|
|||||||
|
|
||||||
## Принципы раздела
|
## Принципы раздела
|
||||||
|
|
||||||
- **Клиент — в `infrastructure/`.** Каждый внешний сервис — отдельный модуль слоя `infrastructure/{service-name}/`.
|
- **Клиент — в `infra/`.** Каждый внешний сервис — отдельный модуль слоя `infra/{service-name}/`.
|
||||||
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
|
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
|
||||||
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
|
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
|
||||||
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
|
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
|
||||||
@@ -40,7 +40,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure,
|
|||||||
|
|
||||||
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
|
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
|
||||||
|
|
||||||
- [Realtime](./realtime.md) — клиент realtime в `infrastructure/`, потребление через `useSWRSubscription` или прямые подписки.
|
- [Realtime](./realtime.md) — клиент realtime в `infra/`, потребление через `useSWRSubscription` или прямые подписки.
|
||||||
|
|
||||||
## Что даёт раздел
|
## Что даёт раздел
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure,
|
|||||||
|
|
||||||
- Где живёт код работы с API и почему именно там.
|
- Где живёт код работы с API и почему именно там.
|
||||||
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
|
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
|
||||||
- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infrastructure/{service-name}/hooks/`.
|
- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infra/{service-name}/hooks/`.
|
||||||
- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
|
- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
|
||||||
- Как подключать realtime-источники в общую модель работы с данными.
|
- Как подключать realtime-источники в общую модель работы с данными.
|
||||||
- Какие правила обязательны и какие отклонения допустимы.
|
- Какие правила обязательны и какие отклонения допустимы.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRS
|
|||||||
|
|
||||||
## Принципы
|
## Принципы
|
||||||
|
|
||||||
- **Клиент realtime — в `infrastructure/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
|
- **Клиент realtime — в `infra/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
|
||||||
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
|
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
|
||||||
- **Использование на клиенте — два сценария:**
|
- **Использование на клиенте — два сценария:**
|
||||||
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
|
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
|
||||||
@@ -19,7 +19,7 @@ keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRS
|
|||||||
## Размещение клиента
|
## Размещение клиента
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/
|
src/infra/
|
||||||
└── {channel-name}/
|
└── {channel-name}/
|
||||||
├── connection.ts # установление соединения, реконнект
|
├── connection.ts # установление соединения, реконнект
|
||||||
├── subscribe.ts # subscribe(topic, handler) → unsubscribe
|
├── subscribe.ts # subscribe(topic, handler) → unsubscribe
|
||||||
@@ -33,7 +33,7 @@ src/infrastructure/
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import useSWRSubscription from 'swr/subscription'
|
import useSWRSubscription from 'swr/subscription'
|
||||||
import { subscribe } from 'infrastructure/notifications'
|
import { subscribe } from 'infra/notifications'
|
||||||
|
|
||||||
export function NotificationCounter() {
|
export function NotificationCounter() {
|
||||||
const { data: count } = useSWRSubscription(
|
const { data: count } = useSWRSubscription(
|
||||||
@@ -56,7 +56,7 @@ export function NotificationCounter() {
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { subscribe } from 'infrastructure/notifications'
|
import { subscribe } from 'infra/notifications'
|
||||||
import { showToast } from 'ui/toast'
|
import { showToast } from 'ui/toast'
|
||||||
|
|
||||||
export function NotificationsToaster() {
|
export function NotificationsToaster() {
|
||||||
@@ -74,6 +74,6 @@ export function NotificationsToaster() {
|
|||||||
|
|
||||||
## Запрет прямых соединений
|
## Запрет прямых соединений
|
||||||
|
|
||||||
Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infrastructure/`.
|
Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infra/`.
|
||||||
|
|
||||||
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ https://petstore3.swagger.io/api/v3/openapi.json
|
|||||||
Имена модуля:
|
Имена модуля:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/pet-store-api/
|
src/infra/pet-store-api/
|
||||||
petStoreApi
|
petStoreApi
|
||||||
pet-store-api.generated.ts
|
pet-store-api.generated.ts
|
||||||
```
|
```
|
||||||
@@ -31,7 +31,7 @@ pet-store-api.generated.ts
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infrastructure/pet-store-api/generated -n pet-store-api.generated"
|
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infra/pet-store-api/generated -n pet-store-api.generated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -53,7 +53,7 @@ npm run codegen:pet-store-api
|
|||||||
Ожидаемый результат:
|
Ожидаемый результат:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/pet-store-api/generated/
|
src/infra/pet-store-api/generated/
|
||||||
└── pet-store-api.generated.ts
|
└── pet-store-api.generated.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ petStoreApi.pet.getPetById(...)
|
|||||||
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/client.ts
|
// src/infra/pet-store-api/client.ts
|
||||||
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
||||||
|
|
||||||
const httpClient = new HttpClient({
|
const httpClient = new HttpClient({
|
||||||
@@ -102,7 +102,7 @@ export const petStoreApi = new Api(httpClient)
|
|||||||
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/biocad-less-api/
|
src/infra/biocad-less-api/
|
||||||
├── generated/
|
├── generated/
|
||||||
│ └── biocad-less-api.generated.ts
|
│ └── biocad-less-api.generated.ts
|
||||||
├── types/
|
├── types/
|
||||||
@@ -115,7 +115,7 @@ src/infrastructure/biocad-less-api/
|
|||||||
Пример расширения generated-типа:
|
Пример расширения generated-типа:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/biocad-less-api/types/term.ts
|
// src/infra/biocad-less-api/types/term.ts
|
||||||
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
|
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
|
||||||
|
|
||||||
declare module '../generated/biocad-less-api.generated' {
|
declare module '../generated/biocad-less-api.generated' {
|
||||||
@@ -149,7 +149,7 @@ export type TermRecordItemExtended = Omit<
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/biocad-less-api/types/index.ts
|
// src/infra/biocad-less-api/types/index.ts
|
||||||
export type { TermRecordItemExtended } from './term'
|
export type { TermRecordItemExtended } from './term'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -158,18 +158,18 @@ export type { TermRecordItemExtended } from './term'
|
|||||||
## Публичный API
|
## Публичный API
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/index.ts
|
// src/infra/pet-store-api/index.ts
|
||||||
export { petStoreApi } from './client'
|
export { petStoreApi } from './client'
|
||||||
export type { Pet } from './generated/pet-store-api.generated'
|
export type { Pet } from './generated/pet-store-api.generated'
|
||||||
export * from './hooks'
|
export * from './hooks'
|
||||||
```
|
```
|
||||||
|
|
||||||
Наружу импортируют только из `infrastructure/pet-store-api`, не из `generated/`.
|
Наружу импортируют только из `infra/pet-store-api`, не из `generated/`.
|
||||||
|
|
||||||
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
|
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/biocad-less-api/index.ts
|
// src/infra/biocad-less-api/index.ts
|
||||||
export type { TermRecordItemExtended } from './types'
|
export type { TermRecordItemExtended } from './types'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: GET-хуки REST-клиента
|
title: GET-хуки REST-клиента
|
||||||
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
||||||
keywords: [rest, swr, get-хуки, client components, infrastructure]
|
keywords: [rest, swr, get-хуки, client components, infra]
|
||||||
---
|
---
|
||||||
|
|
||||||
# GET-хуки REST-клиента
|
# GET-хуки REST-клиента
|
||||||
@@ -13,7 +13,7 @@ GET-хуки REST-клиента — прозрачные SWR-обёртки н
|
|||||||
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
|
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/
|
src/infra/
|
||||||
└── pet-store-api/
|
└── pet-store-api/
|
||||||
├── client.ts
|
├── client.ts
|
||||||
├── generated/
|
├── generated/
|
||||||
@@ -41,7 +41,7 @@ src/infrastructure/
|
|||||||
## Пример списка
|
## Пример списка
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import type { SWRConfiguration } from 'swr'
|
import type { SWRConfiguration } from 'swr'
|
||||||
import { petStoreApi } from '../client'
|
import { petStoreApi } from '../client'
|
||||||
@@ -74,7 +74,7 @@ import { SWRConfig, unstable_serialize } from 'swr'
|
|||||||
import {
|
import {
|
||||||
getPetListKey,
|
getPetListKey,
|
||||||
petStoreApi,
|
petStoreApi,
|
||||||
} from 'infrastructure/pet-store-api'
|
} from 'infra/pet-store-api'
|
||||||
|
|
||||||
export default function PetsLayout({ children }: { children: ReactNode }) {
|
export default function PetsLayout({ children }: { children: ReactNode }) {
|
||||||
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
@@ -102,7 +102,7 @@ const { data: pets } = useGetPetList('available')
|
|||||||
## Пример detail-запроса
|
## Пример detail-запроса
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/use-get-pet-detail.hook.ts
|
// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import type { SWRConfiguration } from 'swr'
|
import type { SWRConfiguration } from 'swr'
|
||||||
import { petStoreApi } from '../client'
|
import { petStoreApi } from '../client'
|
||||||
@@ -141,23 +141,23 @@ const key = isReady ? getPetDetailKey(id) : null
|
|||||||
## Экспорт
|
## Экспорт
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/index.ts
|
// src/infra/pet-store-api/hooks/index.ts
|
||||||
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
|
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
|
||||||
export type { PetStatus } from './use-get-pet-list.hook'
|
export type { PetStatus } from './use-get-pet-list.hook'
|
||||||
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
|
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/index.ts
|
// src/infra/pet-store-api/index.ts
|
||||||
export { petStoreApi } from './client'
|
export { petStoreApi } from './client'
|
||||||
export type { Pet } from './generated/pet-store-api.generated'
|
export type { Pet } from './generated/pet-store-api.generated'
|
||||||
export * from './hooks'
|
export * from './hooks'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Где заканчивается infrastructure
|
## Где заканчивается infra
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Хорошо: infrastructure, прозрачный GET-хук
|
// Хорошо: infra, прозрачный GET-хук
|
||||||
const { data: pets } = useGetPetList('available')
|
const { data: pets } = useGetPetList('available')
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ const { data } = useSWR(
|
|||||||
() => petStoreApi.pet.findPetsByStatus({ status }),
|
() => petStoreApi.pet.findPetsByStatus({ status }),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Плохо — несколько GET внутри infrastructure-хука
|
// Плохо — несколько GET внутри infra-хука
|
||||||
export const usePetDashboard = () => {
|
export const usePetDashboard = () => {
|
||||||
const available = useGetPetList('available')
|
const available = useGetPetList('available')
|
||||||
const sold = useGetPetList('sold')
|
const sold = useGetPetList('sold')
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: Создание клиента
|
title: Создание клиента
|
||||||
description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
|
description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
|
||||||
keywords: [rest, клиент, infrastructure, методы, openapi, get-хуки, swr]
|
keywords: [rest, клиент, infra, методы, openapi, get-хуки, swr]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Создание клиента
|
# Создание клиента
|
||||||
|
|
||||||
REST-клиент — это infrastructure-модуль, через который проект работает с внешним REST API.
|
REST-клиент — это infra-модуль, через который проект работает с внешним REST API.
|
||||||
|
|
||||||
На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
|
На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ GET-хуки именуются с префиксом `useGet`: `useGetPetList`,
|
|||||||
## Структура модуля
|
## Структура модуля
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/{service-name}/
|
src/infra/{service-name}/
|
||||||
├── client.ts # самописная оболочка и инстанс клиента
|
├── client.ts # самописная оболочка и инстанс клиента
|
||||||
├── generated/ или methods/ # методы API
|
├── generated/ или methods/ # методы API
|
||||||
├── hooks/ # GET-хуки REST-клиента
|
├── hooks/ # GET-хуки REST-клиента
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Ручное создание
|
title: Ручное создание
|
||||||
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
||||||
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrastructure]
|
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infra]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ручное создание
|
# Ручное создание
|
||||||
@@ -13,7 +13,7 @@ keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrast
|
|||||||
## Что нужно создать
|
## Что нужно создать
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/
|
src/infra/
|
||||||
└── pet-project-api/
|
└── pet-project-api/
|
||||||
├── methods/
|
├── methods/
|
||||||
│ └── posts.ts
|
│ └── posts.ts
|
||||||
@@ -43,7 +43,7 @@ src/infrastructure/
|
|||||||
DTO запросов и ответов живут в `types/`. `client.ts` не содержит DTO и доменные типы.
|
DTO запросов и ответов живут в `types/`. `client.ts` не содержит DTO и доменные типы.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/types/post.ts
|
// src/infra/pet-project-api/types/post.ts
|
||||||
export type PostDto = {
|
export type PostDto = {
|
||||||
id: string
|
id: string
|
||||||
slug: string
|
slug: string
|
||||||
@@ -57,14 +57,14 @@ export type PostListQueryDto = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/types/index.ts
|
// src/infra/pet-project-api/types/index.ts
|
||||||
export type { PostDto, PostListQueryDto } from './post'
|
export type { PostDto, PostListQueryDto } from './post'
|
||||||
```
|
```
|
||||||
|
|
||||||
Типы, которые нужны только базовому транспорту, можно держать отдельно:
|
Типы, которые нужны только базовому транспорту, можно держать отдельно:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/types/client.ts
|
// src/infra/pet-project-api/types/client.ts
|
||||||
export type QueryParams = Record<string, string | number | boolean>
|
export type QueryParams = Record<string, string | number | boolean>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ export type QueryParams = Record<string, string | number | boolean>
|
|||||||
Ошибка API тоже относится к REST-модулю.
|
Ошибка API тоже относится к REST-модулю.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts
|
// src/infra/pet-project-api/errors/pet-project-api.error.ts
|
||||||
export class PetProjectApiError extends Error {
|
export class PetProjectApiError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly status: number,
|
public readonly status: number,
|
||||||
@@ -90,7 +90,7 @@ export class PetProjectApiError extends Error {
|
|||||||
`client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв.
|
`client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/client.ts
|
// src/infra/pet-project-api/client.ts
|
||||||
import { PetProjectApiError } from './errors/pet-project-api.error'
|
import { PetProjectApiError } from './errors/pet-project-api.error'
|
||||||
import type { QueryParams } from './types/client'
|
import type { QueryParams } from './types/client'
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ export class PetProjectApiClient {
|
|||||||
Методы группируются по сущностям в `methods/`. Они не знают про React, SWR и UI.
|
Методы группируются по сущностям в `methods/`. Они не знают про React, SWR и UI.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/methods/posts.ts
|
// src/infra/pet-project-api/methods/posts.ts
|
||||||
import type { PetProjectApiClient } from '../client'
|
import type { PetProjectApiClient } from '../client'
|
||||||
import type { PostDto, PostListQueryDto } from '../types/post'
|
import type { PostDto, PostListQueryDto } from '../types/post'
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export function postsMethods(client: PetProjectApiClient) {
|
|||||||
`index.ts` собирает именованный API-объект и открывает наружу только публичные части модуля.
|
`index.ts` собирает именованный API-объект и открывает наружу только публичные части модуля.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/index.ts
|
// src/infra/pet-project-api/index.ts
|
||||||
import { PetProjectApiClient } from './client'
|
import { PetProjectApiClient } from './client'
|
||||||
import { postsMethods } from './methods/posts'
|
import { postsMethods } from './methods/posts'
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ export type { PostDto, PostListQueryDto } from './types'
|
|||||||
export * from './hooks'
|
export * from './hooks'
|
||||||
```
|
```
|
||||||
|
|
||||||
Внешний код импортирует только из `infrastructure/pet-project-api`, не из внутренних файлов модуля.
|
Внешний код импортирует только из `infra/pet-project-api`, не из внутренних файлов модуля.
|
||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: REST
|
title: REST
|
||||||
description: Как правильно работать с REST API в проекте.
|
description: Как правильно работать с REST API в проекте.
|
||||||
keywords: [rest, api, данные, infrastructure, клиент, swr, стратегии]
|
keywords: [rest, api, данные, infra, клиент, swr, стратегии]
|
||||||
---
|
---
|
||||||
|
|
||||||
# REST
|
# REST
|
||||||
@@ -15,7 +15,7 @@ REST в проекте проходит через два главных эта
|
|||||||
|
|
||||||
## 1. Создание клиента
|
## 1. Создание клиента
|
||||||
|
|
||||||
На этом этапе внешний API оформляется как модуль слоя `infrastructure/`.
|
На этом этапе внешний API оформляется как модуль слоя `infra/`.
|
||||||
|
|
||||||
Клиент отвечает за:
|
Клиент отвечает за:
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ Business-композиция используется, когда просто
|
|||||||
- Нужно преобразовать DTO в доменную модель.
|
- Нужно преобразовать DTO в доменную модель.
|
||||||
- Нужно спрятать бизнес-сценарий за доменным API.
|
- Нужно спрятать бизнес-сценарий за доменным API.
|
||||||
|
|
||||||
Такая логика не пишется в `infrastructure/`. REST-клиент остаётся прозрачным адаптером к API.
|
Такая логика не пишется в `infra/`. REST-клиент остаётся прозрачным адаптером к API.
|
||||||
|
|
||||||
## Пример поверх одного GET-хука
|
## Пример поверх одного GET-хука
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/pets/hooks/use-available-pets.hook.ts
|
// src/business/pets/hooks/use-available-pets.hook.ts
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Доменный список доступных питомцев.
|
* Доменный список доступных питомцев.
|
||||||
@@ -36,13 +36,13 @@ export const useAvailablePets = () => {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`useGetPetList` — infrastructure-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`.
|
`useGetPetList` — infra-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`.
|
||||||
|
|
||||||
## Пример композиции нескольких GET-хуков
|
## Пример композиции нескольких GET-хуков
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/pets/hooks/use-pets-dashboard.hook.ts
|
// src/business/pets/hooks/use-pets-dashboard.hook.ts
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Данные dashboard по питомцам.
|
* Данные dashboard по питомцам.
|
||||||
@@ -64,13 +64,13 @@ export const usePetsDashboard = () => {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Композиция нескольких запросов не добавляется в `infrastructure/pet-store-api/hooks/`, потому что это уже сценарий потребления данных.
|
Композиция нескольких запросов не добавляется в `infra/pet-store-api/hooks/`, потому что это уже сценарий потребления данных.
|
||||||
|
|
||||||
## Пример auth-состояния
|
## Пример auth-состояния
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/auth/hooks/use-auth-state.hook.ts
|
// src/business/auth/hooks/use-auth-state.hook.ts
|
||||||
import { useGetCurrentUser } from 'infrastructure/backend-api'
|
import { useGetCurrentUser } from 'infra/backend-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Состояние авторизации текущего пользователя.
|
* Состояние авторизации текущего пользователя.
|
||||||
@@ -107,7 +107,7 @@ src/business/
|
|||||||
## Что запрещено
|
## Что запрещено
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Плохо — business-смысл внутри infrastructure-хука
|
// Плохо — business-смысл внутри infra-хука
|
||||||
export const useGetPetList = (status: PetStatus) => {
|
export const useGetPetList = (status: PetStatus) => {
|
||||||
const query = useSWR(...)
|
const query = useSWR(...)
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ keywords: [rest, client components, swr, get-хук, client state]
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
import type { PetStatus } from 'infrastructure/pet-store-api'
|
import type { PetStatus } from 'infra/pet-store-api'
|
||||||
|
|
||||||
const statuses: PetStatus[] = ['available', 'pending', 'sold']
|
const statuses: PetStatus[] = ['available', 'pending', 'sold']
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ export function PetTabs() {
|
|||||||
Хук добавляется в REST-модуль сервиса:
|
Хук добавляется в REST-модуль сервиса:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Не создавайте локальный `useSWR` в компоненте.
|
Не создавайте локальный `useSWR` в компоненте.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ keywords: [rest, swr, fallback, initial data, client hooks, unstable_serialize,
|
|||||||
## Ключ хука
|
## Ключ хука
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
export const getPetListKey = (status: PetStatus) =>
|
export const getPetListKey = (status: PetStatus) =>
|
||||||
['pet-store-api', 'pet', 'list', status] as const
|
['pet-store-api', 'pet', 'list', status] as const
|
||||||
```
|
```
|
||||||
@@ -46,7 +46,7 @@ import { SWRConfig, unstable_serialize } from 'swr'
|
|||||||
import {
|
import {
|
||||||
getPetListKey,
|
getPetListKey,
|
||||||
petStoreApi,
|
petStoreApi,
|
||||||
} from 'infrastructure/pet-store-api'
|
} from 'infra/pet-store-api'
|
||||||
|
|
||||||
type PetsLayoutProps = {
|
type PetsLayoutProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -78,7 +78,7 @@ export default async function PetsLayout({ children }: PetsLayoutProps) {
|
|||||||
```tsx
|
```tsx
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
export function PetList() {
|
export function PetList() {
|
||||||
const { data: pets, isLoading } = useGetPetList('available')
|
const { data: pets, isLoading } = useGetPetList('available')
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ keywords: [rest, promise.all, параллельные запросы, server co
|
|||||||
## Хорошо
|
## Хорошо
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetsDashboardScreen } from 'screens/pets-dashboard'
|
import { PetsDashboardScreen } from 'screens/pets-dashboard'
|
||||||
|
|
||||||
export default async function PetsDashboardPage() {
|
export default async function PetsDashboardPage() {
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ keywords: [rest, promise, suspense, streaming, server components]
|
|||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/page.tsx
|
// src/app/(routes)/pets/page.tsx
|
||||||
import { Suspense } from 'react'
|
import { Suspense } from 'react'
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetListSection } from 'widgets/pet-list-section'
|
import { PetListSection } from 'widgets/pet-list-section'
|
||||||
import { PetListSkeleton } from 'widgets/pet-list-section'
|
import { PetListSkeleton } from 'widgets/pet-list-section'
|
||||||
import type { Pet } from 'infrastructure/pet-store-api'
|
import type { Pet } from 'infra/pet-store-api'
|
||||||
|
|
||||||
export default function PetsPage() {
|
export default function PetsPage() {
|
||||||
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ SSR/dynamic rendering нужен, когда данные зависят от т
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/page.tsx
|
// src/app/(routes)/pets/page.tsx
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetsScreen } from 'screens/pets'
|
import { PetsScreen } from 'screens/pets'
|
||||||
|
|
||||||
export default async function PetsPage() {
|
export default async function PetsPage() {
|
||||||
@@ -48,7 +48,7 @@ export default async function PetsPage() {
|
|||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/[id]/page.tsx
|
// src/app/(routes)/pets/[id]/page.tsx
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetDetailScreen } from 'screens/pet-detail'
|
import { PetDetailScreen } from 'screens/pet-detail'
|
||||||
|
|
||||||
type PetPageProps = {
|
type PetPageProps = {
|
||||||
|
|||||||
Reference in New Issue
Block a user