1212 lines
64 KiB
Plaintext
1212 lines
64 KiB
Plaintext
|
|
|
|||
|
|
<a name="1-assistentmd"></a>
|
|||
|
|
|
|||
|
|
## Для ассистента
|
|||
|
|
|
|||
|
|
- Всегда используй Русский язык для общения и генерации документации/комментариев/коммитов.
|
|||
|
|
- Всегда следуй этим правилам при генерации кода и ответах.
|
|||
|
|
- Всегда пиши план действий перед генерацией кода.
|
|||
|
|
- Всегда спрашивай разрешения у пользователя перед генерацией кода.
|
|||
|
|
- Всегда проверяй, что код соответствует линтингу и форматированию.
|
|||
|
|
- Всегда сверяйся с чек-листом при генерации кода.
|
|||
|
|
- Не предлагай решения, которые противоречат этим правилам этого файла.
|
|||
|
|
- Если не уверен — уточни у пользователя, не гадай, не придумывай.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="2-check-listmd"></a>
|
|||
|
|
|
|||
|
|
## Обязательность чек-листов
|
|||
|
|
|
|||
|
|
- Все чек-листы, приведённые в правилах, обязательны к исполнению.
|
|||
|
|
- Ассистент обязан сверяться с чек-листом при выполнении любой задачи, связанной с кодом.
|
|||
|
|
- Нельзя сокращать, игнорировать или опускать пункты чек-листа — каждый пункт должен быть выполнен или явно отмечен как невыполнимый с объяснением причины.
|
|||
|
|
- В каждом ответе, связанном с генерацией или изменением кода, ассистент обязан ссылаться на соответствующий чек-лист и подтверждать его выполнение.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="3-general-principlesmd"></a>
|
|||
|
|
|
|||
|
|
## Общие принципы
|
|||
|
|
|
|||
|
|
- Использовать **TypeScript** для всех файлов логики и компонентов.
|
|||
|
|
- Использовать **FSD (Feature-Sliced Design)**: разделять код на features, entities, processes, widgets, shared.
|
|||
|
|
- Использовать **TSDoc** для документации функций, компонентов, типов, аргументов, возвращаемых значений.
|
|||
|
|
- Использовать **React** (функциональные компоненты, хуки).
|
|||
|
|
- Использовать **Mantine UI** для UI-компонентов.
|
|||
|
|
- Использовать **Axios** для работы с API.
|
|||
|
|
- Использовать **SWR** для data fetching (GET-запросы).
|
|||
|
|
- Использовать **Zod** для валидации форм.
|
|||
|
|
- Использовать **Zustand** для глобального состояния.
|
|||
|
|
- Использовать **i18n** для локализации.
|
|||
|
|
- Использовать **Vitest** для тестирования.
|
|||
|
|
- Использовать **PostCSS модули** для стилизации.
|
|||
|
|
- Использовать **BEM** для именований классов в стилях
|
|||
|
|
- Использовать **Mobile First** подход для написания стилей.
|
|||
|
|
- Использовать **Context7** примеров использования библиотек.
|
|||
|
|
- Использовать **i18n** (i18next) для локализации всех пользовательских текстов.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="4-arkhitekturamd"></a>
|
|||
|
|
|
|||
|
|
## Архитектура проекта
|
|||
|
|
В проекте используется FSD (Feature-Sliced Design) архитектура.
|
|||
|
|
|
|||
|
|
- **FSD-границы**
|
|||
|
|
- Не нарушать границы слоёв (например, feature не может импортировать из widgets).
|
|||
|
|
- Бизнес-логика должна быть вынесена в хуки или сервисы.
|
|||
|
|
- **Импорты**
|
|||
|
|
- Внутри слоя — относительные импорты.
|
|||
|
|
- Между слоями — абсолютные импорты.
|
|||
|
|
- **Требования**
|
|||
|
|
- Не смешивать логику разных слоёв.
|
|||
|
|
- Не хранить бизнес-логику в UI-компонентах.
|
|||
|
|
- **Именование**
|
|||
|
|
- Файлы и папки kebab-case.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="5-code-stylemd"></a>
|
|||
|
|
|
|||
|
|
## 5. Стиль кода
|
|||
|
|
|
|||
|
|
- **Строгая типизация**: всегда указывать типы для пропсов, возвращаемых значений, параметров функций.
|
|||
|
|
- **Ранние возвраты** (early return) для повышения читаемости.
|
|||
|
|
- **Мемоизация**: Старайся оптимизировать код если это возможно.
|
|||
|
|
- **Документирование**: Документируем ТОЛЬКО ОПИСАНИЕ (функций, компонентов, типов и их полей).
|
|||
|
|
- **any, unknown** запрещено использовать без крайней необходимости.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="6-namingmd"></a>
|
|||
|
|
|
|||
|
|
## Именование файлов и папок
|
|||
|
|
- Папка компонента: kebab-case, совпадает с названием компонента, пример: `component-name`.
|
|||
|
|
- React-компонент: kebab-case, совпадает с названием компонента, пример: `component-name.tsx`.
|
|||
|
|
- Стили: kebab-case, шаблон: `<style-name>.module.css`, пример: `style-name.module.css`.
|
|||
|
|
- Интерфейсы: kebab-case, шаблон: `<interface-name>.interface.ts`, пример: `interface-name.interface.ts`.
|
|||
|
|
- Типы: kebab-case, шаблон: `<type-name>.type.ts`, пример: `type-name.type.ts`.
|
|||
|
|
- Enum: kebab-case, шаблон: `<enum-name>.enum.ts`, пример: `enum-name.enum.ts`.
|
|||
|
|
- Схемы: kebab-case, шаблон: `<schema-name>.schema.ts`, пример: `schema-name.schema.ts`.
|
|||
|
|
- Локализация: kebab-case, пример: `ru.json`, `en.json`.
|
|||
|
|
- Утилиты: kebab-case, шаблон: `<util-name>.util.ts`, пример: `util-name.util.ts`
|
|||
|
|
- React Hooks: kebab-case, шаблон: `use-<hook-name>.hook.ts`, пример: `use-hook-name.hook.ts`
|
|||
|
|
- Хранилища состояния компонента: kebab-case, шаблон: `<store-name>.store.ts`, пример: `store-name.store.ts`
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="7-docsmd"></a>
|
|||
|
|
|
|||
|
|
## Правило для документирования кода
|
|||
|
|
|
|||
|
|
- Документировать разрешено только описание (назначение) функций, компонентов, типов, интерфейсов, enum и их полей.
|
|||
|
|
- Строго запрещено документировать параметры, возвращаемые значения, типы пропсов, аргументы, возвращаемые значения функций, компоненты, хуки и т.д.
|
|||
|
|
- В интерфейсах, типах и enum разрешено документировать только смысл (описание) каждого поля или значения.
|
|||
|
|
- В React-компонентах, функциях, хранилищах, схемах, утилитах разрешено документировать только назначение (описание), без детализации параметров и возвращаемых значений.
|
|||
|
|
- Описание должно быть кратким, информативным и реально помогать понять структуру и бизнес-логику.
|
|||
|
|
- Не допускается избыточная или дублирующая очевидное документация.
|
|||
|
|
- В конце описания всегда ставить точку.
|
|||
|
|
|
|||
|
|
**Примеры правильного документирования**
|
|||
|
|
```tsx
|
|||
|
|
/**
|
|||
|
|
* Список задач пользователя.
|
|||
|
|
*/
|
|||
|
|
export const TodoList = memo(() => { ... });
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Интерфейс задачи.
|
|||
|
|
*/
|
|||
|
|
export interface TodoItem {
|
|||
|
|
/** Уникальный идентификатор задачи. */
|
|||
|
|
id: string;
|
|||
|
|
/** Текст задачи. */
|
|||
|
|
text: string;
|
|||
|
|
/** Статус выполнения задачи. */
|
|||
|
|
completed: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Перечисление фильтров задач.
|
|||
|
|
*/
|
|||
|
|
export enum TodoFilter {
|
|||
|
|
/** Все задачи. */
|
|||
|
|
All = 'all',
|
|||
|
|
/** Только активные задачи. */
|
|||
|
|
Active = 'active',
|
|||
|
|
/** Только выполненные задачи. */
|
|||
|
|
Completed = 'completed',
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Примеры неправильного документирования**
|
|||
|
|
```ts
|
|||
|
|
// ❌ Не нужно:/
|
|||
|
|
/**
|
|||
|
|
* @param id - идентификатор задачи
|
|||
|
|
* @returns объект задачи
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// ❌ Не нужно:/
|
|||
|
|
/**
|
|||
|
|
* @param props - пропсы компонента
|
|||
|
|
* @returns JSX.Element
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// ❌ Не нужно:/
|
|||
|
|
/**
|
|||
|
|
* id — идентификатор задачи
|
|||
|
|
* text — текст задачи
|
|||
|
|
* completed — статус выполнения
|
|||
|
|
*/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="8-typingmd"></a>
|
|||
|
|
|
|||
|
|
## Общие правила типизации
|
|||
|
|
|
|||
|
|
> Данный раздел определяет единые требования к типизации для всего проекта. Соблюдение этих правил обеспечивает читаемость, предсказуемость и безопасность кода.
|
|||
|
|
|
|||
|
|
- Использовать только строгую типизацию TypeScript для всех файлов логики, компонентов, хуков, API, сторов и утилит.
|
|||
|
|
- Всегда явно указывать типы для:
|
|||
|
|
- Пропсов компонентов
|
|||
|
|
- Параметров функций и методов
|
|||
|
|
- Возвращаемых значений функций и методов
|
|||
|
|
- Всех переменных состояния (в том числе в store)
|
|||
|
|
- Всех значимых переменных и констант, если их тип не очевиден из присваивания
|
|||
|
|
- Не использовать `any` и `unknown` без крайней необходимости. Если использование неизбежно — обязательно добавить комментарий с обоснованием.
|
|||
|
|
- Все интерфейсы, типы и enum всегда размещать в папке `types/` на своём уровне абстракции (например, `features/todo/types/`).
|
|||
|
|
- Для DTO всегда использовать отдельную папку `dto/` на уровне сущности или слоя.
|
|||
|
|
- Для сложных структур использовать отдельные интерфейсы или типы, размещая их в соответствующих файлах в папке `types/`.
|
|||
|
|
- Для DTO, enum, схем и других сущностей — всегда создавать отдельные типы/интерфейсы с осмысленными именами.
|
|||
|
|
- Ключи enum всегда писать ЗАГЛАВНЫМИ_БУКВАМИ (SCREAMING_SNAKE_CASE).
|
|||
|
|
- Не использовать неявное приведение типов и не полагаться на автоматический вывод, если это может снизить читаемость или безопасность.
|
|||
|
|
- Для массивов и объектов всегда указывать тип элементов/ключей.
|
|||
|
|
- Для возвращаемых значений асинхронных функций всегда указывать тип Promise.
|
|||
|
|
- Типизацию коллбеков и функций, передаваемых в пропсы, указывать инлайн, не выносить в отдельные типы.
|
|||
|
|
- Для типизации внешних библиотек использовать официальные типы или создавать собственные декларации при необходимости.
|
|||
|
|
- Не использовать устаревшие или не рекомендуемые паттерны типизации (например, `Function`, `Object`, `{}`).
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Примеры
|
|||
|
|
|
|||
|
|
#### Интерфейс и типы для сущностей (всегда в папке types/)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// features/todo/types/todo-item.interface.ts
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Интерфейс задачи.
|
|||
|
|
*/
|
|||
|
|
export interface TodoItem {
|
|||
|
|
/** Уникальный идентификатор задачи. */
|
|||
|
|
id: string;
|
|||
|
|
/** Текст задачи. */
|
|||
|
|
text: string;
|
|||
|
|
/** Статус выполнения задачи. */
|
|||
|
|
completed: boolean;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Типизация enum (всегда в папке types/)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// features/todo/types/todo-status.enum.ts
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Перечисление статусов задачи.
|
|||
|
|
*/
|
|||
|
|
export enum TodoStatus {
|
|||
|
|
/** Активная задача. */
|
|||
|
|
ACTIVE = 'active',
|
|||
|
|
/** Выполненная задача. */
|
|||
|
|
COMPLETED = 'completed',
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Типизация пропсов компонента
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import { FC, memo } from 'react';
|
|||
|
|
import { TodoItem } from './types/todo-item.interface';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Список задач.
|
|||
|
|
*/
|
|||
|
|
export interface TodoListProps {
|
|||
|
|
/** Массив задач. */
|
|||
|
|
items: TodoItem[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const TodoList: FC<TodoListProps> = memo(({ items }) => (
|
|||
|
|
<ul>
|
|||
|
|
{items.map((item) => (
|
|||
|
|
<li key={item.id}>{item.text}</li>
|
|||
|
|
))}
|
|||
|
|
</ul>
|
|||
|
|
));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Типизация функций и коллбеков (инлайн)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
/**
|
|||
|
|
* Функция фильтрации задач.
|
|||
|
|
*/
|
|||
|
|
export const getCompletedTodos = (items: TodoItem[]): TodoItem[] => {
|
|||
|
|
return items.filter((t) => t.completed);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Колбэк для обработки клика (инлайн).
|
|||
|
|
*/
|
|||
|
|
const handleClick = (id: string): void => {
|
|||
|
|
console.log('Clicked:', id);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Типизация асинхронных функций
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
/**
|
|||
|
|
* Получить задачи с сервера.
|
|||
|
|
*/
|
|||
|
|
export const fetchTodos = async (): Promise<TodoItem[]> => {
|
|||
|
|
const response = await fetch('/api/todos');
|
|||
|
|
return response.json();
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Типизация состояния в store (интерфейс в types/)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// features/todo/types/todo-store.interface.ts
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Состояние хранилища задач.
|
|||
|
|
*/
|
|||
|
|
export interface TodoStoreState {
|
|||
|
|
/** Массив задач. */
|
|||
|
|
items: TodoItem[];
|
|||
|
|
/** Добавить задачу. */
|
|||
|
|
addTodo: (item: TodoItem) => void;
|
|||
|
|
/** Удалить задачу. */
|
|||
|
|
removeTodo: (id: string) => void;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Типизация DTO (всегда в папке dto/)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// features/todo/dto/create-todo.dto.ts
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* DTO для создания задачи.
|
|||
|
|
*/
|
|||
|
|
export interface CreateTodoDto {
|
|||
|
|
/** Текст задачи. */
|
|||
|
|
text: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// features/todo/dto/todo-response.dto.ts
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* DTO ответа сервера.
|
|||
|
|
*/
|
|||
|
|
export interface TodoResponseDto {
|
|||
|
|
/** Созданная задача. */
|
|||
|
|
todo: TodoItem;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Типизация внешних библиотек
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import type { AxiosResponse } from 'axios';
|
|||
|
|
|
|||
|
|
export const getData = async (): Promise<AxiosResponse<TodoItem[]>> => {
|
|||
|
|
// ...
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
### Чек-лист проверки типизации
|
|||
|
|
|
|||
|
|
- [ ] Все пропсы компонентов явно типизированы через интерфейс или тип в папке `types/`.
|
|||
|
|
- [ ] Все параметры и возвращаемые значения функций и методов явно типизированы.
|
|||
|
|
- [ ] Все переменные состояния (в том числе в store) имеют явные типы.
|
|||
|
|
- [ ] Все интерфейсы, типы и enum размещены в папке `types/` на своём уровне абстракции.
|
|||
|
|
- [ ] Ключи всех enum написаны ЗАГЛАВНЫМИ_БУКВАМИ (SCREAMING_SNAKE_CASE).
|
|||
|
|
- [ ] Все DTO размещены в папке `dto/` на своём уровне абстракции.
|
|||
|
|
- [ ] Не используется `any` и `unknown` без крайней необходимости и поясняющего комментария.
|
|||
|
|
- [ ] Для сложных структур используются отдельные интерфейсы или типы.
|
|||
|
|
- [ ] Для массивов и объектов указан тип элементов/ключей.
|
|||
|
|
- [ ] Для асинхронных функций указан тип Promise с конкретным типом результата.
|
|||
|
|
- [ ] Типы коллбеков и функций, передаваемых в пропсы, указаны инлайн.
|
|||
|
|
- [ ] Не используются устаревшие типы (`Function`, `Object`, `{}`).
|
|||
|
|
- [ ] Для внешних библиотек используются официальные типы или собственные декларации.
|
|||
|
|
- [ ] Нет неявного приведения типов, все типы читаемы и прозрачны.
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="9-localizationmd"></a>
|
|||
|
|
|
|||
|
|
## Правила использования локализации
|
|||
|
|
|
|||
|
|
- Все пользовательские тексты должны быть вынесены в локализационные файлы.
|
|||
|
|
- Для каждого компонента создавать папку `locales/` с файлами `ru.json`, `en.json` и т.д.
|
|||
|
|
- Новые namespace обязательно регистрировать в экземпляре i18n (см. `app/i18n.ts`).
|
|||
|
|
- В коде использовать только функцию перевода из i18n, не использовать "жёстко" прописанные строки.
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="10-storesmd"></a>
|
|||
|
|
|
|||
|
|
## Сторы (Stores)
|
|||
|
|
|
|||
|
|
> В этом разделе собраны основные правила и рекомендации по созданию и оформлению сторов. Следуйте этим принципам, чтобы обеспечить чистую архитектуру, удобство поддержки и единый стиль работы с состоянием в проекте.
|
|||
|
|
> В проекте для организации состояния используется только библиотека Zustand.
|
|||
|
|
|
|||
|
|
### Структура
|
|||
|
|
- Store размещается в файле `<store-name>.store.ts` в папке `stores/` на своём уровне абстракции согласно архитектуре проекта.
|
|||
|
|
- Интерфейс состояния описывается в этом же файле с суффиксом `State` (PascalCase).
|
|||
|
|
- Для каждого store создаётся отдельный хук доступа (например, `useTodoStore`).
|
|||
|
|
- Для глобальных сторов используйте только `shared/store`.
|
|||
|
|
|
|||
|
|
### Именование
|
|||
|
|
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|||
|
|
- Файл store — `<store-name>.store.ts` (kebab-case).
|
|||
|
|
- Имя интерфейса состояния — PascalCase с суффиксом `State`.
|
|||
|
|
- Имя хука — camelCase с префиксом `use`.
|
|||
|
|
|
|||
|
|
### Требования
|
|||
|
|
- В store допускается только хранение состояния и методы управления им, без бизнес-логики, асинхронных операций и side-effects (см. раздел "Правила организации и использовалья Store").
|
|||
|
|
- Для методов, изменяющих состояние через set, если используется функция — тело функции в фигурных скобках, return с новой строки после стрелки.
|
|||
|
|
- Не дублируйте логику между сторами.
|
|||
|
|
|
|||
|
|
### Типизация
|
|||
|
|
- Всегда указывайте типы для всех полей состояния и методов.
|
|||
|
|
- Не используйте неявное приведение типов и не полагайтесь на автоматический вывод, если это может снизить читаемость или безопасность.
|
|||
|
|
|
|||
|
|
### Документирование
|
|||
|
|
- Документируйте только назначение store и смысл полей, строго по [правилам документирования кода](#правило-для-документирования-кода).
|
|||
|
|
|
|||
|
|
### Экспорт
|
|||
|
|
- Экспортируйте хук доступа к store и интерфейс состояния через `index.ts` слоя/компонента.
|
|||
|
|
|
|||
|
|
### Примеры
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import { create } from 'zustand';
|
|||
|
|
import { TodoItem } from './types/todo-item.interface';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Состояние хранилища задач.
|
|||
|
|
*/
|
|||
|
|
export interface TodoStoreState {
|
|||
|
|
/** Массив задач. */
|
|||
|
|
items: TodoItem[];
|
|||
|
|
/** Добавить задачу. */
|
|||
|
|
addTodo: (item: TodoItem) => void;
|
|||
|
|
/** Удалить задачу. */
|
|||
|
|
removeTodo: (id: string) => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Хук для доступа к хранилищу задач.
|
|||
|
|
*/
|
|||
|
|
export const useTodoStore = create<TodoStoreState>((set) => ({
|
|||
|
|
items: [],
|
|||
|
|
addTodo: (item) => set((state) => {
|
|||
|
|
return {
|
|||
|
|
items: [...state.items, item],
|
|||
|
|
};
|
|||
|
|
}),
|
|||
|
|
removeTodo: (id) => set((state) => {
|
|||
|
|
return {
|
|||
|
|
items: state.items.filter((t) => t.id !== id),
|
|||
|
|
};
|
|||
|
|
}),
|
|||
|
|
}));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Чек-лист
|
|||
|
|
|
|||
|
|
- [ ] Store размещён в `stores/<store-name>.store.ts` на своём уровне абстракции согласно архитектуре проекта.
|
|||
|
|
- [ ] Именование файлов и сущностей соответствует [правилам именования файлов и папок](#правила-именования-файлов-и-папок).
|
|||
|
|
- [ ] Все поля и методы строго типизированы (см. [общие правила типизации](#общие-правила-типизации)).
|
|||
|
|
- [ ] В store только состояние и методы управления им, без бизнес-логики и side-effects.
|
|||
|
|
- [ ] Для методов, изменяющих состояние через set, используется функция с return с новой строки.
|
|||
|
|
- [ ] Документировано только назначение store и смысл полей (см. [правила документирования кода](#правило-для-документирования-кода)).
|
|||
|
|
- [ ] Нет неиспользуемого или невалидного кода.
|
|||
|
|
- [ ] Экспорт через индексный файл.
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="11-cssmd"></a>
|
|||
|
|
|
|||
|
|
## Правила оформления и стилизации CSS-кода
|
|||
|
|
|
|||
|
|
- **Препроцессоры**
|
|||
|
|
Используй PostCSS и модули для стилизации.
|
|||
|
|
|
|||
|
|
- **Архитектура написания стилей**
|
|||
|
|
Используй подход **Mobile First**
|
|||
|
|
Используй CSS переменные для стилизации.
|
|||
|
|
Используй Custom Media Queries для адаптивных стилей.
|
|||
|
|
Используй BEM для именования классов.
|
|||
|
|
Между каждым CSS-правилом (селектором) должен быть один пустой сброс строки, пример:
|
|||
|
|
```css
|
|||
|
|
.todo-list {
|
|||
|
|
max-width: 600px;
|
|||
|
|
padding: var(--space-3);
|
|||
|
|
|
|||
|
|
@media (--md) {
|
|||
|
|
max-width: 800px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.todo-list__text {
|
|||
|
|
font-size: 18px;
|
|||
|
|
|
|||
|
|
@media (--md) {
|
|||
|
|
font-size: 22px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Запрещено писать правила подряд без пустой строки:
|
|||
|
|
```css
|
|||
|
|
/* Так делать нельзя! */
|
|||
|
|
.todo-list { ... }
|
|||
|
|
.todo-list__text { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **Методология именования классов**
|
|||
|
|
Использовать методологию **BEM** для именования классов.
|
|||
|
|
- Блок: kebab-case, пример `.user-bar { }`
|
|||
|
|
- Елемент: kebab-case, соеденный с блоком двойным нижним подчеркиванием, пример `.user-bar__slide { }`
|
|||
|
|
- Модификатор: kebab-case, отдельный самостоятельный класс, **не соединяется** с блоком/елементом, имя модификатора всегда начинается с нижнего подчеркивания, пример: `._red { }`
|
|||
|
|
|
|||
|
|
- **Единицы измерения**
|
|||
|
|
Используй `px` как основная единица измирения, так-же допускается использовать остальные единицы измерения если того требует реализуемый дизайн.
|
|||
|
|
|
|||
|
|
- **Импорт стилей**
|
|||
|
|
Стили компонента должны импортироваться только внутри соответствующего компонента.
|
|||
|
|
Запрещено импортировать стили одного компонента в другой.
|
|||
|
|
Запрещено импортировать `css переменные` в файлы стилей, они доступны глобально.
|
|||
|
|
Запрещено импортировать `custom media` в файлы стилей, они доступны глобально.
|
|||
|
|
|
|||
|
|
- **Переменные**
|
|||
|
|
Все значения переменных нужно писать в `/shared/styles` или в Mantine ThemeProvider.
|
|||
|
|
Все что не является цветами, брекпоинтами, отступами, скруглением допускаются использоваться в компонентах.
|
|||
|
|
Обязательное создавай CSS перменные для:
|
|||
|
|
- "Цветов", пример: `--color-danger: red;`.
|
|||
|
|
- "Брекпоинты", описываем в (Сustom media) пример: `@custom-media --md (min-width: 62em);`.
|
|||
|
|
- "Отспупы (--space)", , пример: `--space-1: 4px;`, `--space-2: 8px;`, `--space-3: 12px;` итд..
|
|||
|
|
- "Скругление углов (--radius)", пример: `--radius-1: 4px;`,`--radius-2: 8px;`,`--radius-3: 12px;` итд..
|
|||
|
|
|
|||
|
|
- **Вложенность селекторов**
|
|||
|
|
Запрещено использовать вложенность селекторов.
|
|||
|
|
Разрешено использовать вложенность только для:
|
|||
|
|
- Псевдо-классов `:hover`, `:active` итд..
|
|||
|
|
- Псевдо-елементов `::before`, `::after`
|
|||
|
|
- Медиа запросов `@media`
|
|||
|
|
- Классы **модификаторы** по методологии BEM
|
|||
|
|
Каждый вложенный селектор отделяется 1 пустой строкой.
|
|||
|
|
|
|||
|
|
- **Медиа запросы**
|
|||
|
|
Строго запрещено использовать `@media` без вложения в селектор.
|
|||
|
|
Строго запрещено использовать в теле `@media` любые селекторы.
|
|||
|
|
Разрешено использовать только Custom Media Queries (например, `@media (--md) {}`).
|
|||
|
|
Запрещено использовать любые произвольные значения breakpoints (например, max-width: 768px).
|
|||
|
|
**Пример как правильно писать @media**
|
|||
|
|
```css
|
|||
|
|
.todo-list {
|
|||
|
|
max-width: 600px;
|
|||
|
|
padding: 24px;
|
|||
|
|
|
|||
|
|
@media (--md) {
|
|||
|
|
max-width: 800px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.todo-list__text {
|
|||
|
|
font-size: 18px;
|
|||
|
|
|
|||
|
|
@media (--md) {
|
|||
|
|
font-size: 22px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
**Пример как неправильно писать @media**
|
|||
|
|
```css
|
|||
|
|
// Медиа запрос не вложен в селектор
|
|||
|
|
@media (--md) {
|
|||
|
|
.todo-list {
|
|||
|
|
max-width: 600px;
|
|||
|
|
padding: 24px;
|
|||
|
|
}
|
|||
|
|
.todo-list__text {
|
|||
|
|
font-size: 18px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Используется стандартный `min-width: 992px` вмето Custom Media Queries
|
|||
|
|
@media (min-width: 992px) {
|
|||
|
|
// Внутри @media запроса используются селекторы
|
|||
|
|
.todo-list {
|
|||
|
|
max-width: 600px;
|
|||
|
|
padding: 24px;
|
|||
|
|
}
|
|||
|
|
.todo-list__text {
|
|||
|
|
font-size: 18px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- **Глобальные стили и сбросы**
|
|||
|
|
Все глобальные стили (например, сбросы) должны располагаться в отдельном файле, например, `src/app/styles/global.css`.
|
|||
|
|
|
|||
|
|
- **Использование Mantine и PostCSS**
|
|||
|
|
Для стандартных визуальных компонентов (кнопки, инпуты, layout, grid, notifications и т.д.) использовать только Mantine и его ThemeProvider.
|
|||
|
|
Запрещено использовать в Mantine компонентах его props/styling, вмето этого нужно добавлять кастомные стили PostCSS.
|
|||
|
|
Кастомные стили допускаются только в случае, если требуемый дизайн невозможно реализовать средствами Mantine.
|
|||
|
|
При написании кастомных стилей стараться использовать переменные и токены Mantine, если это возможно.
|
|||
|
|
|
|||
|
|
- **Порядок CSS-свойств**
|
|||
|
|
В стилях рекомендуется придерживаться логического порядка свойств:
|
|||
|
|
1. Позиционирование (position, top, left, z-index и т.д.)
|
|||
|
|
2. Блочная модель (display, width, height, margin, padding и т.д.)
|
|||
|
|
3. Оформление (background, border, box-shadow и т.д.)
|
|||
|
|
4. Текст (font, color, text-align и т.д.)
|
|||
|
|
5. Прочее (transition, animation и т.д.)
|
|||
|
|
|
|||
|
|
- **Комментарии**
|
|||
|
|
В стилях запрещено использовать комментарии.
|
|||
|
|
|
|||
|
|
- **Дублирования**
|
|||
|
|
Не дублировать стили между компонентами. Общие стили выносить в shared/styles или использовать переменные.
|
|||
|
|
|
|||
|
|
- **Примеры кода стилей**
|
|||
|
|
Пример как хорошо:
|
|||
|
|
```css
|
|||
|
|
/* Блок BEM */
|
|||
|
|
.user-bar {
|
|||
|
|
display: none;
|
|||
|
|
color: black;
|
|||
|
|
|
|||
|
|
/* Медиа запрос custom media и отделяется 1 пустой строкой */
|
|||
|
|
@media (--md) {
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Елемент BEM отделяется 1 пустой строкой*/
|
|||
|
|
.user-bar__button-next {
|
|||
|
|
background-color: #f0f0f0;
|
|||
|
|
|
|||
|
|
/* Псевдо-класс отделяется 1 пустой строкой*/
|
|||
|
|
&:hover {
|
|||
|
|
background-color: #e0e0e0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Модификатор BEM отделяется 1 пустой строкой*/
|
|||
|
|
&._blue {
|
|||
|
|
background-color: #2b2bbe;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Модификатор BEM отделяется 1 пустой строкой*/
|
|||
|
|
&._green {
|
|||
|
|
background-color: #29c53d;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Пример как плохо писать:
|
|||
|
|
```css
|
|||
|
|
.user-bar {
|
|||
|
|
display: none;
|
|||
|
|
color: black;
|
|||
|
|
&__button {
|
|||
|
|
&_next {
|
|||
|
|
background-color: #f0f0f0;
|
|||
|
|
&:hover {
|
|||
|
|
background-color: #e0e0e0;
|
|||
|
|
}
|
|||
|
|
&._blue {
|
|||
|
|
background-color: #2b2bbe;
|
|||
|
|
}
|
|||
|
|
&._green {
|
|||
|
|
background-color: #29c53d;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
@media (min-width: 992px) {
|
|||
|
|
.user-bar {
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Чек лист для проверки стилизации.**
|
|||
|
|
- [ ] Используется PostCSS и CSS-модули для стилизации.
|
|||
|
|
- [ ] Применён подход Mobile First.
|
|||
|
|
- [ ] Именование классов строго по BEM:
|
|||
|
|
- [ ] Модификатор — отдельный класс, начинается с нижнего подчёркивания (например, `._red`, `._active`)
|
|||
|
|
- [ ] Все CSS-переменные (цвета, брейкпоинты, отступы, скругления) определены только в `/shared/styles` или через Mantine ThemeProvider.
|
|||
|
|
- [ ] Для медиа-запросов используются только custom media переменные из `/shared/styles/media.css`.
|
|||
|
|
- [ ] Соблюдается правила вложености селекторов.
|
|||
|
|
- [ ] Соблюдается правила отступов селекторов.
|
|||
|
|
- [ ] Глобальные стили (reset) вынесены в отдельный файл, остальные стили — модульные.
|
|||
|
|
- [ ] Для стандартных UI-элементов используются только компоненты Mantine, кастомные стили — только при необходимости.
|
|||
|
|
- [ ] В Mantine-компонентах не используются props/styling для стилизации, только PostCSS.
|
|||
|
|
- [ ] Кастомные стили используют переменные и токены Mantine, если это возможно.
|
|||
|
|
- [ ] В стилях нет комментариев.
|
|||
|
|
- [ ] Стили компонента импортируются только внутри соответствующего компонента.
|
|||
|
|
- [ ] Нет импорта стилей одного компонента в другой.
|
|||
|
|
- [ ] Нет импорта файлов переменных и custom media — они доступны глобально.
|
|||
|
|
- [ ] Нет дублирования стилей между компонентами, общие стили вынесены в shared/styles или используются переменные.
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="12-componentsmd"></a>
|
|||
|
|
|
|||
|
|
## Правила создания и работы с компонентами.
|
|||
|
|
|
|||
|
|
### 1. Структура компонента
|
|||
|
|
Ассистент при создании/рефакторинге компонента должен **строго** придерживаться следующей структуры файлов и папок:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
component-name/
|
|||
|
|
index.ts
|
|||
|
|
component-name.tsx
|
|||
|
|
styles/
|
|||
|
|
component-name.module.css
|
|||
|
|
locales/
|
|||
|
|
ru.json
|
|||
|
|
en.json
|
|||
|
|
types/
|
|||
|
|
component-name.interface.ts
|
|||
|
|
component-name.type.ts
|
|||
|
|
component-name.enum.ts
|
|||
|
|
schemas/
|
|||
|
|
schema-name.schema.ts
|
|||
|
|
utils/
|
|||
|
|
util-name.util.ts
|
|||
|
|
hooks/
|
|||
|
|
use-hook-name.hook.ts
|
|||
|
|
stores/
|
|||
|
|
store-name.store.ts
|
|||
|
|
ui/
|
|||
|
|
... # вложенные компоненты для component-name
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Пояснения к структуре компонента:
|
|||
|
|
**Обязательные файлы** обязательны для всех компонентов, даже если они пустые.
|
|||
|
|
- component-name/: Папка компонента корень для всего компонента.
|
|||
|
|
- index.ts: экспортирует главный компонент, интерфейс и всё, что может быть переиспользовано.
|
|||
|
|
- component-name.tsx: главный компонент.
|
|||
|
|
- styles/component-name.module.css: стили компонента.
|
|||
|
|
- locales/ru.json: локализация на русском языке.
|
|||
|
|
- locales/en.json: локализация на английском языке.
|
|||
|
|
- types/component-name.interface.ts: интерфейс пропсов компонента.
|
|||
|
|
**Не обязательные файлы** добавляются только при необходимости
|
|||
|
|
- types/component-name.type.ts: типы компонента.
|
|||
|
|
- types/component-name.enum.ts: enum компонента.
|
|||
|
|
- schemas/schema-name.schema.ts: схемы валидации.
|
|||
|
|
- utils/util-name.util.ts: утилиты компонента.
|
|||
|
|
- hooks/use-hook-name.hook.ts: хуки компонента.
|
|||
|
|
- stores/store-name.store.ts: хранилища состояния компонента.
|
|||
|
|
- ui/: Папка для вложенных компонентов.
|
|||
|
|
|
|||
|
|
### Требования к компоненту
|
|||
|
|
- Использовать `memo()` для всех компонентов, которые принимают пропсы.
|
|||
|
|
- Использовать `useMemo` для всех вычислений, которые передаются в пропсы других компонентов.
|
|||
|
|
- Использовать `useCallback` для всех функций/методов, которые передаются в пропсы других компонентов.
|
|||
|
|
|
|||
|
|
### Требования к вложенным компонентам
|
|||
|
|
- Вложенный компонент — это полноценный компонент, который обязан полностью соблюдать все правила, описанные для компонентов (структура, именование, документация, типизация, стилизация и т.д.).
|
|||
|
|
- Все вложенные компоненты размещаются только в папке ui/ основного компонента.
|
|||
|
|
|
|||
|
|
**Пояснение**
|
|||
|
|
Нет необходимости повторять структуру и требования — вложенный компонент подчиняется тем же правилам, что и любой другой компонент, только располагается в папке ui/ родительского компонента.
|
|||
|
|
|
|||
|
|
### Требования к локализации
|
|||
|
|
- Все добавленные локализации обязательно подключать в экземпляр `app/i18n` (чтобы новые namespace были доступны для i18next).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Чек-лист для создания нового компонента
|
|||
|
|
- [ ] Главный компонент размещён в корне и назван по правилу PascalCase.
|
|||
|
|
- [ ] Создан файл стилей в папке `styles/`, имя в kebab-case, используется BEM.
|
|||
|
|
- [ ] Все классы применяются через `className={styles['component-name']}`.
|
|||
|
|
- [ ] Создана папка `locales/` с файлами `ru.json` и `en.json`.
|
|||
|
|
- [ ] Создан файл интерфейса пропсов в папке `types/`, даже если интерфейс пустой.
|
|||
|
|
- [ ] Создан файл `index.ts` с экспортом главного компонента и интерфейса.
|
|||
|
|
- [ ] Внутренние компоненты (если есть) размещены в папке `ui/`.
|
|||
|
|
- [ ] Все важные части кода документированы по TSDoc (см. раздел 16).
|
|||
|
|
- [ ] Остальные файлы (schemas, дополнительные типы, enum) добавлены только при необходимости.
|
|||
|
|
- [ ] Именование файлов и папок соответствует правилам (см. выше).
|
|||
|
|
- [ ] Нет неиспользуемого или невалидного кода.
|
|||
|
|
- [ ] Для компонентов с пропсами используется `React.memo`.
|
|||
|
|
- [ ] Для вычислений, передаваемых в пропсы, используется `useMemo`.
|
|||
|
|
- [ ] Для функций, передаваемых в пропсы, используется `useCallback`.
|
|||
|
|
- [ ] Все тексты вынесены в локализационные файлы и используются через i18n.
|
|||
|
|
- [ ] Новые namespace подключены в экземпляр i18n.
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="13-hooksmd"></a>
|
|||
|
|
|
|||
|
|
## Хуки (React Hooks)
|
|||
|
|
|
|||
|
|
> В проекте для создания пользовательских хуков используется только React (функциональные компоненты и хуки).
|
|||
|
|
> В этом разделе собраны основные правила и рекомендации по созданию и оформлению хуков. Следуйте этим принципам, чтобы обеспечить чистую архитектуру, переиспользуемость и единый стиль работы с хуками в проекте.
|
|||
|
|
|
|||
|
|
### Рекомендации по использованию сторонних хуков
|
|||
|
|
- Если есть возможность, используйте хуки Mantine в компонентах и кастомных хуках для работы с состоянием, темизацией, медиа-запросами и другими возможностями библиотеки.
|
|||
|
|
- Не дублируйте функциональность, уже реализованную в Mantine.
|
|||
|
|
|
|||
|
|
### Структура
|
|||
|
|
- Каждый хук размещается в отдельном файле с именем `use-<hook-name>.hook.ts` в папке `hooks/` на своём уровне абстракции согласно архитектуре проекта.
|
|||
|
|
- Имя хука — в стиле camelCase с префиксом `use` (например, `useTodoFilter`).
|
|||
|
|
- Для сложных возвращаемых структур использовать отдельные типы или интерфейсы, размещая их в папке `types/` на своём уровне абстракции.
|
|||
|
|
|
|||
|
|
### Именование
|
|||
|
|
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|||
|
|
- Файл хука — `use-<hook-name>.hook.ts` (kebab-case).
|
|||
|
|
- Имя хука — camelCase с префиксом `use`.
|
|||
|
|
|
|||
|
|
### Требования
|
|||
|
|
- Хук должен быть строго типизирован: все параметры, возвращаемые значения и внутренние переменные должны иметь явные типы.
|
|||
|
|
- Не хранить бизнес-логику, связанную с несколькими слоями — хук должен быть изолирован в рамках своего слоя/feature.
|
|||
|
|
- Не дублировать логику между хуками — общие части выносить в shared.
|
|||
|
|
- Не использовать side-effects вне useEffect/useLayoutEffect.
|
|||
|
|
- Для мемоизации возвращаемых значений и функций использовать useMemo и useCallback.
|
|||
|
|
- Не использовать устаревшие или неразрешённые паттерны React.
|
|||
|
|
|
|||
|
|
### Типизация
|
|||
|
|
- Всегда явно указывать типы для всех параметров, возвращаемых значений и состояния внутри хука.
|
|||
|
|
- Не используйте неявное приведение типов и не полагайтесь на автоматический вывод, если это может снизить читаемость или безопасность.
|
|||
|
|
|
|||
|
|
### Документирование
|
|||
|
|
- Документируйте только назначение хука (описание), строго по [правилам документирования кода](#правило-для-документирования-кода).
|
|||
|
|
|
|||
|
|
### Экспорт
|
|||
|
|
- Экспортируйте хук только именованным экспортом через `index.ts` слоя/компонента.
|
|||
|
|
|
|||
|
|
### Примеры
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import { useMemo } from 'react';
|
|||
|
|
import { TodoItem } from '../types/todo-item.interface';
|
|||
|
|
import { TodoStatus } from '../types/todo-status.enum';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Хук фильтрации задач по статусу.
|
|||
|
|
*/
|
|||
|
|
export const useTodoFilter = (items: TodoItem[], filter: TodoStatus): TodoItem[] => {
|
|||
|
|
return useMemo(() => {
|
|||
|
|
if (filter === TodoStatus.ALL) return items;
|
|||
|
|
if (filter === TodoStatus.ACTIVE) return items.filter((t) => !t.completed);
|
|||
|
|
return items.filter((t) => t.completed);
|
|||
|
|
}, [items, filter]);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Чек-лист
|
|||
|
|
|
|||
|
|
- [ ] Хук размещён в файле `use-<hook-name>.hook.ts` в папке `hooks/` на своём уровне абстракции согласно архитектуре проекта.
|
|||
|
|
- [ ] Именование файлов и сущностей соответствует [правилам именования файлов и папок](#правила-именования-файлов-и-папок).
|
|||
|
|
- [ ] Все параметры, возвращаемые значения и внутренние переменные строго типизированы.
|
|||
|
|
- [ ] Вся бизнес-логика изолирована в рамках слоя/feature.
|
|||
|
|
- [ ] Нет дублирования логики между хуками.
|
|||
|
|
- [ ] Для мемоизации используется useMemo/useCallback.
|
|||
|
|
- [ ] Не используются side-effects вне useEffect/useLayoutEffect.
|
|||
|
|
- [ ] Документировано только назначение хука (см. [правила документирования кода](#правило-для-документирования-кода)).
|
|||
|
|
- [ ] Нет неиспользуемого или невалидного кода.
|
|||
|
|
- [ ] Экспорт только именованный через индексный файл.
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="14-api-hooksmd"></a>
|
|||
|
|
|
|||
|
|
## Хуки API (React Hooks)
|
|||
|
|
|
|||
|
|
> В проекте для работы с API-хуками используется только React и библиотека SWR для получения данных (GET-запросы).
|
|||
|
|
> В этом разделе собраны основные правила и рекомендации по созданию и оформлению хуков для работы с API. Следуйте этим принципам, чтобы обеспечить чистую архитектуру, переиспользуемость и единый стиль работы с API-хуками в проекте.
|
|||
|
|
|
|||
|
|
### Описание и назначение API-хуков
|
|||
|
|
|
|||
|
|
API-хуки предназначены для получения данных с сервера (GET-запросы) и используются в компонентах или других хуках.
|
|||
|
|
В проекте для этого применяется библиотека SWR, которая обеспечивает кэширование, автоматическое обновление и удобную работу с асинхронными запросами.
|
|||
|
|
|
|||
|
|
**Fetcher** — это функция, которую использует SWR для выполнения запроса к API. В проекте fetcher обычно экспортируется из файла клиента (например, `backendFetcher` из `shared/api/backend/client.ts`) и инкапсулирует логику обращения к конкретному API-клиенту.
|
|||
|
|
|
|||
|
|
**API-клиент** — это отдельный модуль (папка), отвечающий за взаимодействие с конкретным внешним или внутренним API.
|
|||
|
|
API-клиент включает:
|
|||
|
|
- инициализацию экземпляра HTTP-клиента (например, Axios),
|
|||
|
|
- настройку базового URL, интерцепторов и общих обработчиков ошибок,
|
|||
|
|
- организацию всех сущностей и методов для работы с этим API (например, users, auth, orders и т.д.),
|
|||
|
|
- экспорт всех функций, типов и fetcher через индексные файлы.
|
|||
|
|
|
|||
|
|
Каждый API-клиент размещается в папке `src/shared/api/<client-name>/` и имеет собственную структуру согласно архитектуре проекта.
|
|||
|
|
|
|||
|
|
### Структура
|
|||
|
|
- Каждый API-хук размещается в отдельном файле с именем `use-<method-name>.hook-api.ts` в папке `hooks/api/<client-name>/` на своём уровне абстракции согласно архитектуре проекта.
|
|||
|
|
- Имя хука — в стиле camelCase с префиксом `use` (например, `useGetUser`).
|
|||
|
|
- Для сложных возвращаемых структур используйте отдельные типы или интерфейсы, размещая их в папке `types/` на своём уровне абстракции.
|
|||
|
|
|
|||
|
|
### Именование
|
|||
|
|
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|||
|
|
- Файл хука — `use-<method-name>.hook-api.ts` (kebab-case).
|
|||
|
|
- Имя хука — camelCase с префиксом `use`.
|
|||
|
|
|
|||
|
|
### Требования
|
|||
|
|
- Хук должен быть строго типизирован: все параметры, возвращаемые значения и внутренние переменные должны иметь явные типы.
|
|||
|
|
- Для получения данных используйте только SWR.
|
|||
|
|
- Не дублируйте логику между хуками — общие части выносите в shared.
|
|||
|
|
- Не используйте side-effects вне useEffect/useLayoutEffect.
|
|||
|
|
|
|||
|
|
### Типизация
|
|||
|
|
- Всегда явно указывайте типы для всех параметров, возвращаемых значений и состояния внутри хука.
|
|||
|
|
- Придерживайтесь [общих правил типизации проекта](#общие-правила-типизации).
|
|||
|
|
- Не используйте неявное приведение типов и не полагайтесь на автоматический вывод, если это может снизить читаемость или безопасность.
|
|||
|
|
|
|||
|
|
### Документирование
|
|||
|
|
- Документируйте только назначение хука (описание), строго по [правилам документирования кода](#правило-для-документирования-кода).
|
|||
|
|
|
|||
|
|
### Экспорт
|
|||
|
|
- Экспортируйте хук только именованным экспортом через `index.ts` слоя/компонента.
|
|||
|
|
|
|||
|
|
### Пример API хука
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// use-get-me.hook-api.ts
|
|||
|
|
import useSWR from 'swr';
|
|||
|
|
import { backendFetcher } from 'shared/api/backend/client';
|
|||
|
|
import { UserDto } from 'shared/api/backend/entities/users/get-me.api';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Хук для получения информации о текущем пользователе.
|
|||
|
|
*/
|
|||
|
|
export const useGetMe = () => {
|
|||
|
|
const { data, error, isLoading } = useSWR<UserDto>('/users/me', backendFetcher);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
data,
|
|||
|
|
error,
|
|||
|
|
isLoading,
|
|||
|
|
};
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Пример использования хука в компоненте
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import React from 'react';
|
|||
|
|
import { useGetMe } from 'shared/hooks/api/backend/use-get-me.hook-api';
|
|||
|
|
|
|||
|
|
export const UserInfo: React.FC = () => {
|
|||
|
|
const { data, error, isLoading } = useGetMe();
|
|||
|
|
|
|||
|
|
if (isLoading) {
|
|||
|
|
return <div>Загрузка...</div>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
return <div>Ошибка загрузки данных</div>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!data) {
|
|||
|
|
return <div>Нет данных о пользователе</div>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<div>Имя: {data.name}</div>
|
|||
|
|
<div>Email: {data.email}</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Чек-лист для создания API-хука
|
|||
|
|
|
|||
|
|
- [ ] Для каждого GET-запроса создан отдельный хук.
|
|||
|
|
- [ ] Хук размещён в `hooks/api/<client-name>/use-<method-name>.hook-api.ts` на своём уровне абстракции согласно архитектуре проекта.
|
|||
|
|
- [ ] Именование файлов и сущностей соответствует [правилам именования файлов и папок](#правила-именования-файлов-и-папок).
|
|||
|
|
- [ ] Используется SWR для получения данных.
|
|||
|
|
- [ ] Все параметры, возвращаемые значения и внутренние переменные строго типизированы.
|
|||
|
|
- [ ] Нет дублирования логики между хуками.
|
|||
|
|
- [ ] Не используются side-effects вне useEffect/useLayoutEffect.
|
|||
|
|
- [ ] Документировано только назначение хука (см. [правила документирования кода](#правило-для-документирования-кода)).
|
|||
|
|
- [ ] Нет неиспользуемого или невалидного кода.
|
|||
|
|
- [ ] Экспорт только именованный через индексный файл.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Чек-лист для использования API-хука
|
|||
|
|
|
|||
|
|
- [ ] Импортируется только нужный хук через публичные экспорты (`index.ts`).
|
|||
|
|
- [ ] Использование хука строго по назначению (только для получения данных).
|
|||
|
|
- [ ] Если требуется получить данные через GET-запрос в компоненте — обязательно используется соответствующий API-хук.
|
|||
|
|
**Запрещено вызывать GET-методы API напрямую в компонентах, только через хуки.**
|
|||
|
|
- [ ] Обработка состояний загрузки, ошибки и данных реализована корректно.
|
|||
|
|
- [ ] Не происходит дублирования логики, связанной с получением данных.
|
|||
|
|
- [ ] Нет неиспользуемого или невалидного кода.
|
|||
|
|
|
|||
|
|
|
|||
|
|
<a name="15-apimd"></a>
|
|||
|
|
|
|||
|
|
## API
|
|||
|
|
|
|||
|
|
> В этом разделе собраны основные правила и рекомендации по созданию, оформлению и использованию API-клиентов и функций для работы с сервером. Следуйте этим принципам, чтобы обеспечить единый стиль, безопасность и удобство поддержки API-слоя в проекте.
|
|||
|
|
|
|||
|
|
### Описание и назначение API-клиента
|
|||
|
|
|
|||
|
|
API-клиент — это модуль (папка), отвечающий за взаимодействие с конкретным внешним или внутренним API.
|
|||
|
|
В проекте для HTTP-запросов используется только Axios.
|
|||
|
|
API-клиент инкапсулирует:
|
|||
|
|
- инициализацию экземпляра Axios,
|
|||
|
|
- настройку базового URL, интерцепторов, обработчиков ошибок,
|
|||
|
|
- организацию всех сущностей и методов для работы с этим API (например, users, auth, orders и т.д.),
|
|||
|
|
- экспорт всех функций, типов и fetcher через индексные файлы.
|
|||
|
|
|
|||
|
|
Каждый API-клиент размещается в папке `src/shared/api/<client-name>/` и имеет собственную структуру согласно архитектуре проекта.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Использование методов API
|
|||
|
|
|
|||
|
|
- Все методы API должны использоваться строго внутри блока `try...catch`.
|
|||
|
|
- При вызове методов API всегда используйте полный путь, например:
|
|||
|
|
`await api.backend.createUser({ email, password });`
|
|||
|
|
- Запрещено вызывать методы API вне блока `try...catch` даже в тестах, утилитах и других вспомогательных функциях.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Структура клиента
|
|||
|
|
```text
|
|||
|
|
src/shared/api/backend/
|
|||
|
|
│
|
|||
|
|
├── client.ts
|
|||
|
|
├── index.ts
|
|||
|
|
└── entities/
|
|||
|
|
├── users/
|
|||
|
|
│ ├── get-me.api.ts
|
|||
|
|
│ ├── create-user.api.ts
|
|||
|
|
│ ├── update-user.api.ts
|
|||
|
|
│ └── index.ts
|
|||
|
|
├── auth/
|
|||
|
|
│ ├── login.api.ts
|
|||
|
|
│ ├── register.api.ts
|
|||
|
|
│ └── index.ts
|
|||
|
|
└── index.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Описание ключевых элементов
|
|||
|
|
|
|||
|
|
- **client.ts**
|
|||
|
|
Экземпляр Axios с настройками, интерцепторами, экспортом fetcher для SWR.
|
|||
|
|
|
|||
|
|
- **index.ts**
|
|||
|
|
Главная точка экспорта: экспортирует client, fetcher, все сущности и их методы.
|
|||
|
|
|
|||
|
|
- **entities/**
|
|||
|
|
Папка для бизнес-сущностей (например, users, auth, orders и т.д.).
|
|||
|
|
|
|||
|
|
- **<entity>/**
|
|||
|
|
Папка для отдельной сущности. Имя — в kebab-case, отражает бизнес-область (например, users, auth).
|
|||
|
|
|
|||
|
|
- **<operation>.api.ts**
|
|||
|
|
Файл для каждой операции (CRUD, спец. действия).
|
|||
|
|
Внутри:
|
|||
|
|
- DTO (интерфейсы запроса/ответа)
|
|||
|
|
- Функция, реализующая запрос через client
|
|||
|
|
|
|||
|
|
- **index.ts (внутри <entity>/)**
|
|||
|
|
Экспортирует все методы и типы этой сущности.
|
|||
|
|
|
|||
|
|
- **index.ts (внутри entities/)**
|
|||
|
|
Экспортирует все сущности (users, auth и т.д.).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Именование
|
|||
|
|
|
|||
|
|
- Соблюдайте [правила именования файлов и папок](#правила-именования-файлов-и-папок):
|
|||
|
|
- Файл клиента — `client.ts`.
|
|||
|
|
- Файл функции — `<operation>-<entity>.api.ts` (например, `create-user.api.ts`).
|
|||
|
|
- DTO — в папке `dto/` (например, `create-user.dto.ts`).
|
|||
|
|
- Все функции и типы экспортируются через индексные файлы на каждом уровне (сущность, entities, клиент).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Требования
|
|||
|
|
|
|||
|
|
- Для каждого действия (CRUD, спец. действия) — отдельная функция и файл.
|
|||
|
|
- Все функции используют общий экземпляр Axios из `client.ts`.
|
|||
|
|
- Все функции строго типизированы (используются DTO).
|
|||
|
|
- DTO объявляется в отдельном файле в папке `dto/` перед функцией, которая его использует.
|
|||
|
|
- Для каждого GET метода обязательно должен быть создан API-хук.
|
|||
|
|
- Все API-хуки должны создаваться строго по [документации раздела "Хуки для API"](#хуки-для-api-api-hooks).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Типизация
|
|||
|
|
|
|||
|
|
- Все функции и DTO строго типизированы.
|
|||
|
|
- Все интерфейсы, типы и enum размещены в папке `types/` на своём уровне абстракции.
|
|||
|
|
- Все DTO размещены в папке `dto/` на своём уровне абстракции.
|
|||
|
|
- Придерживайтесь [общих правил типизации проекта](#общие-правила-типизации).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Документирование
|
|||
|
|
|
|||
|
|
- Документируйте только назначение функций и DTO, строго по [правилам документирования кода](#правило-для-документирования-кода).
|
|||
|
|
- В описании указывается только смысл функции/типа.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Экспорт
|
|||
|
|
|
|||
|
|
- Все функции и типы экспортируются через индексные файлы на каждом уровне (сущность, entities, клиент).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Примеры
|
|||
|
|
|
|||
|
|
#### Пример клиента API
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// client.ts
|
|||
|
|
import axios, { AxiosInstance } from "axios";
|
|||
|
|
export { AxiosError, isAxiosError } from 'axios';
|
|||
|
|
export type { AxiosResponse } from 'axios';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Экземпляр HTTP-клиента для работы с backend API.
|
|||
|
|
*/
|
|||
|
|
export const backendHttpClient: AxiosInstance = axios.create({
|
|||
|
|
baseURL: '/api',
|
|||
|
|
timeout: 10000,
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Интерцептор запроса
|
|||
|
|
backendHttpClient.interceptors.request.use(
|
|||
|
|
(config) => {
|
|||
|
|
// Здесь можно добавить авторизационные заголовки или другую логику
|
|||
|
|
return config;
|
|||
|
|
},
|
|||
|
|
(error) => Promise.reject(error)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// Интерцептор ответа
|
|||
|
|
backendHttpClient.interceptors.response.use(
|
|||
|
|
(response) => response,
|
|||
|
|
(error) => {
|
|||
|
|
// Здесь можно обработать ошибки (например, показать уведомление)
|
|||
|
|
return Promise.reject(error);
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Пример DTO
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// dto/create-user.dto.ts
|
|||
|
|
/**
|
|||
|
|
* DTO для создания пользователя.
|
|||
|
|
*/
|
|||
|
|
export interface CreateUserDto {
|
|||
|
|
/** Email пользователя. */
|
|||
|
|
email: string;
|
|||
|
|
/** Пароль пользователя. */
|
|||
|
|
password: string;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Пример API-функции
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
// create-user.api.ts
|
|||
|
|
import { backendHttpClient } from '../client';
|
|||
|
|
import { CreateUserDto } from './dto/create-user.dto';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Создать пользователя.
|
|||
|
|
*/
|
|||
|
|
export const createUser = (data: CreateUserDto) =>
|
|||
|
|
backendHttpClient.post('/users', data);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Пример index.ts (в папке сущности)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
export * from './create-user.api';
|
|||
|
|
export * from './get-user.api';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Пример использования API-функции в компоненте
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import React, { useState } from 'react';
|
|||
|
|
import { api } from 'shared/api';
|
|||
|
|
|
|||
|
|
export const CreateUserForm: React.FC = () => {
|
|||
|
|
const [email, setEmail] = useState('');
|
|||
|
|
const [password, setPassword] = useState('');
|
|||
|
|
|
|||
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|||
|
|
e.preventDefault();
|
|||
|
|
try {
|
|||
|
|
await api.backend.createUser({ email, password });
|
|||
|
|
console.log('Пользователь создан!');
|
|||
|
|
} catch {
|
|||
|
|
console.log('Ошибка создания пользователя');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<form onSubmit={handleSubmit}>
|
|||
|
|
<input
|
|||
|
|
type="email"
|
|||
|
|
value={email}
|
|||
|
|
onChange={e => setEmail(e.target.value)}
|
|||
|
|
placeholder="Email"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
<input
|
|||
|
|
type="password"
|
|||
|
|
value={password}
|
|||
|
|
onChange={e => setPassword(e.target.value)}
|
|||
|
|
placeholder="Пароль"
|
|||
|
|
required
|
|||
|
|
/>
|
|||
|
|
<button type="submit">Создать пользователя</button>
|
|||
|
|
</form>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Чек-лист для создания клиента
|
|||
|
|
- [ ] Новый клиент размещён в src/shared/api/<client-name>/.
|
|||
|
|
- [ ] В корне клиента есть client.ts (экземпляр Axios) и index.ts (главный экспорт).
|
|||
|
|
- [ ] Все бизнес-сущности размещены в entities/, каждая — в отдельной папке.
|
|||
|
|
- [ ] Для каждой операции создан отдельный файл <operation>.api.ts с DTO и функцией.
|
|||
|
|
- [ ] DTO объявлен непосредственно перед функцией.
|
|||
|
|
- [ ] В каждой папке сущности есть свой index.ts для экспорта методов и типов.
|
|||
|
|
- [ ] В папке entities/ есть общий index.ts для экспорта всех сущностей.
|
|||
|
|
- [ ] Все экспорты организованы через индексные файлы.
|
|||
|
|
- [ ] Для каждого GET-метода создан отдельный SWR-хук (см. правила API-хуков).
|
|||
|
|
- [ ] Нет дублирования кода и неиспользуемых файлов.
|
|||
|
|
|
|||
|
|
### Чек-лист для использования API
|
|||
|
|
- [ ] Импортируется только нужный метод через публичные экспорты (index.ts).
|
|||
|
|
- [ ] Все вызовы API обёрнуты в try...catch.
|
|||
|
|
- [ ] Используются только строго типизированные методы.
|
|||
|
|
- [ ] Не происходит обращения к Axios напрямую — только через client.
|
|||
|
|
- [ ] Нет дублирования логики и неиспользуемого кода.
|