Files
frontend-style-guide/RULES.md
2026-01-30 02:02:32 +03:00

631 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<a name="1-tech-stackmd"></a>
# Технологии и библиотеки
Базовый стек технологий и библиотек, на который опираются проекты и примеры в документации.
## Что используем обычно
- **TypeScript** — типизация всей логики и компонентов.
- **FSD (Feature-Sliced Design)** — структура проекта и границы модулей.
- **React + Next.js** — основной стек для UI и приложения.
- **Mantine UI** — базовые UI-компоненты.
- **SWR** — получение данных по GET (кеширование и revalidate).
- **Zustand** — глобальное состояние.
- **i18next (i18n)** — локализация всех пользовательских текстов.
- **Vitest** — тестирование.
- **PostCSS Modules** — изоляция стилей.
- **Mobile First** — подход к адаптивной верстке.
<a name="2-architecturemd"></a>
# Архитектура
Архитектура построена на FSD (FeatureSliced Design) и строгих границах модулей.
Цель — разделить ответственность, упростить сопровождение и контроль зависимостей.
## Принципы
- Разделять UI, бизнес-логику и инфраструктуру.
- Держать зависимости однонаправленными.
- Открывать наружу только публичный API модулей.
- Не допускать циклических зависимостей.
## Слои
- **app** — инициализация приложения, роутинг, конфигурации, глобальные провайдеры.
- **screens** — экраны и их композиция.
- **layouts** — каркас и шаблоны страниц.
- **widgets** — крупные блоки интерфейса, собирающие несколько сценариев.
- **features** — отдельные пользовательские действия и сценарии.
- **entities** — бизнес-сущности и их модель.
- **shared** — переиспользуемая инфраструктура, утилиты и базовые UIкомпоненты.
## Правила зависимостей
- Допустимые импорты идут сверху вниз: `app → screens → layouts → widgets → features → entities → shared`.
- Импорты между слоями — через публичный API.
- Внутри одного слоя — относительные импорты.
## Публичный API модулей
- Каждый модуль экспортирует наружу только то, что нужно другим слоям.
- Внешние импорты идут только через `index`‑файл модуля.
- Внутренние файлы не импортируются напрямую извне.
## Границы ответственности
- Бизнес‑логика не размещается в UIкомпонентах.
- UIкомпоненты должны быть максимально простыми и предсказуемыми.
- Связь между независимыми сценариями поднимается на уровень выше.
## Типовые ошибки
- Импорт из более высокого слоя в более низкий.
- Смешивание логики нескольких слоёв в одном модуле.
- Прямые импорты внутренних файлов, минуя публичный API.
<a name="3-code-stylemd"></a>
# Стиль кода
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
## Отступы
- 2 пробела (не табы).
## Длина строк
- Ориентироваться на 100 символов, но превышение допустимо, если строка читается легко.
- Переносить выражение на новые строки, когда строка становится плохо читаемой.
- Не переносить строку внутри строковых литералов без необходимости.
**Хорошо**
```ts
const config = createRequestConfig(
endpoint,
{
headers: {
'X-Request-Id': requestId,
'X-User-Id': userId,
},
params: {
page,
pageSize,
sort: 'createdAt',
},
},
timeoutMs,
);
```
**Плохо**
```ts
// Плохо: длинная строка с вложенными структурами плохо читается.
const config = createRequestConfig(endpoint, { headers: { 'X-Request-Id': requestId, 'X-User-Id': userId }, params: { page, pageSize, sort: 'createdAt' } }, timeoutMs);
```
## Кавычки
- В JavaScript/TypeScript использовать одинарные кавычки.
- В JSX/TSX для атрибутов использовать двойные кавычки.
- Шаблонные строки использовать только при интерполяции или многострочном тексте.
**Хорошо**
```ts
const label = 'Сохранить';
const title = `Привет, ${name}`;
```
```tsx
<input type="text" placeholder="Введите имя" />
```
**Плохо**
```ts
// Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки.
const label = "Сохранить";
const title = 'Привет, ' + name;
```
```tsx
// Плохо: одинарные кавычки в JSX-атрибутах.
<input type='text' placeholder='Введите имя' />
```
## Точки с запятой и запятые
- Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным.
- В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна.
## Импорты
- В именованных импортах использовать пробелы внутри фигурных скобок.
- Типы импортировать через `import type`.
- `default` импорт и экспорт избегать, использовать именованные.
- Избегать импорта всего модуля через `*`.
**Хорошо**
```ts
import { MyComponent } from 'MyComponent';
import type { User } from '../model/types';
```
**Плохо**
```ts
// Плохо: default импорт и отсутствие пробелов в именованном импорте.
import MyComponent from 'MyComponent';
import type {User} from '../model/types';
```
## Ранние возвраты (early return)
- Использовать ранние возвраты для упрощения чтения.
- Избегать `else` после `return`.
**Хорошо**
```ts
const getName = (user?: { name: string }) => {
if (!user) {
return 'Гость';
}
return user.name;
};
```
**Плохо**
```ts
// Плохо: лишний else после return усложняет чтение.
const getName = (user?: { name: string }) => {
if (user) {
return user.name;
} else {
return 'Гость';
}
};
```
## Форматирование объектов и массивов
- В многострочных объектах каждое свойство на новой строке.
- В многострочных массивах каждый элемент на новой строке.
- Объекты и массивы можно писать в одну строку, если длина строки не превышает 100 символов.
- В однострочных объектах и массивах использовать пробелы после запятых.
**Хорошо**
```ts
const roles = ['admin', 'editor', 'viewer'];
const options = { id: 1, name: 'User' };
const config = {
url: '/api/users',
method: 'GET',
params: { page: 1, pageSize: 20 },
};
```
**Плохо**
```ts
// Плохо: нет пробелов после запятых и объект слишком длинный для одной строки.
const roles = ['admin','editor','viewer'];
const options = { id: 1,name: 'User' };
const config = { url: '/api/users', method: 'GET', params: { page: 1, pageSize: 20 } };
```
<a name="4-namingmd"></a>
# Именование
Именование должно быть предсказуемым, коротким и отражать смысл сущности.
## Базовые правила
| Что | Рекомендуется |
| ---------------- | ---------------------- |
| Папки | `kebab-case` |
| Файлы | `kebab-case` |
| Переменные | `camelCase` |
| Константы | `SCREAMING_SNAKE_CASE` |
| Классы | `PascalCase` |
| React-компоненты | `PascalCase` |
| Хуки | `useSomething` |
| CSS классы | `camelCase` |
## Архитектурный неймспейс
Соглашение о суффиксах, которые обозначают слой (уровень абстракции), роль или тип файла.
- Суффиксы используются для обозначения слоя, роли или типа файла.
- Суффиксы всегда пишутся в единственном числе.
- Формат имени: `name.<suffix>.ts` или `name.<suffix>.tsx`.
**UI и слои FSD**
- `.screen.tsx` — экран
- `.layout.tsx` — layout
- `.widget.tsx` — виджет
- `.feature.tsx` — UI фичи
- `.entity.tsx` — UI сущности
- `.ui.tsx` — UIкомпонент
**Логика и модель**
- `.store.ts` — стор
- `.service.ts` — сервис
**Типы и контракты**
- `.type.ts` — типы и интерфейсы
- `.interface.ts` — файл с интерфейсами (если нужен отдельный контракт)
- `.enum.ts` — enum
- `.dto.ts` — внешние DTO
- `.schema.ts` — схемы валидации
- `.constant.ts` — константы
- `.config.ts` — конфигурация
**Утилиты и хелперы**
- `.util.ts` — утилиты
- `.helper.ts` — вспомогательные функции
- `.lib.ts` — вспомогательные функции
**Тесты**
- `.test.ts` / `.test.tsx`
- `.mock.ts`
**Хорошо**
```text
src/
├── screens/
│ └── main/
│ ├── main.screen.tsx
│ └── index.ts
├── features/
│ └── auth-by-email/
│ ├── ui/
│ │ └── login-form.ui.tsx
│ ├── auth-by-email.feature.tsx
│ └── index.ts
└── shared/
└── ui/
└── icon/
├── icon.ui.tsx
└── icon.module.css
```
**Плохо**
```text
// Плохо: нет единых правил для слоёв и публичных файлов.
src/
├── screens/
│ └── Main/
│ └── Main.tsx
└── features/
└── authByEmail/
└── login-form.tsx
```
## Булевы значения
- Использовать префиксы `is`, `has`, `can`, `should`.
**Хорошо**
```ts
const isReady = true;
const hasAccess = false;
const canSubmit = true;
const shouldRedirect = false;
```
**Плохо**
```ts
// Плохо: неясное булево значение без префикса.
const ready = true;
const access = false;
const submit = true;
```
## События и обработчики
- Обработчики начинать с `handle`.
- События и колбэки начинать с `on`.
**Хорошо**
```ts
const handleSubmit = () => { ... };
const onSubmit = () => { ... };
```
**Плохо**
```ts
// Плохо: неочевидное назначение имени.
const submitClick = () => { ... };
```
## Коллекции
- Для массивов использовать имена во множественном числе.
- Для словарей/мап — использовать суффиксы `ById`, `Map`, `Dict`.
**Хорошо**
```ts
const users = [];
const usersById = {} as Record<string, User>;
const userIds = ['u1', 'u2'];
const ordersMap = new Map<string, Order>();
const featureFlagsDict = { beta: true, legacy: false } as Record<string, boolean>;
```
**Плохо**
```ts
// Плохо: имя не отражает, что это коллекция.
const user = [];
// Плохо: словарь назван как массив.
const usersMap = [];
// Плохо: по имени непонятно, что это словарь.
const users = {} as Record<string, User>;
```
<a name="5-documentationmd"></a>
# Документирование
Документирование должно помогать понять назначение сущности, а не дублировать её типы или очевидные детали.
## Правила
- Документировать только назначение функций, компонентов, типов, интерфейсов и enum.
- Не документировать параметры, возвращаемые значения, типы пропсов и очевидные детали.
- В интерфейсах, типах и enum описывать только смысл поля или значения.
- Описание должно быть кратким, информативным и завершаться точкой.
## Примеры
**Хорошо**
```ts
/**
* Список задач пользователя.
*/
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 объект задачи
*/
// Плохо: описание очевидных деталей.
/**
* id — идентификатор задачи
* text — текст задачи
* completed — статус выполнения
*/
```
<a name="6-typingmd"></a>
# Типизация
Типизация обязательна для всех публичных интерфейсов, функций и компонентов.
Цель — предсказуемость, безопасность и автодополнение.
## Общие правила
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
- Предпочитать `type` для описания сущностей и `interface` для расширяемых контрактов.
- Избегать `any` и `unknown` без необходимости.
- Не использовать `ts-ignore`, кроме крайних случаев с явным комментарием причины.
## Типы для компонентов
- Типизировать параметры и публичный интерфейс компонента.
- Дефолтные значения описывать явно в коде.
**Хорошо**
```tsx
/**
* Параметры кнопки.
*/
interface IOwnProps extends HTMLAttributes<HTMLDivElement> {
/** Текст кнопки. */
label: string;
/** Обработчик клика по кнопке. */
onClick: () => void;
}
/**
* Кнопка с пользовательскими стилями.
*/
export const Button:FC<IOwnProps> = ({ className, label, onClick, ...htmlAttr }) => {
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
button
</div>
)
}
```
**Плохо**
```tsx
// Плохо: параметры не типизированы.
export const Button = (props) => (
<button type="button" onClick={props.onClick}>
{props.label}
</button>
);
```
## Функции
- Для публичных функций указывать возвращаемый тип.
- Не полагаться на неявный вывод для важных API.
**Хорошо**
```ts
export const formatPrice = (value: number): string => {
return `${value} ₽`;
};
```
**Плохо**
```ts
// Плохо: нет явного возвращаемого типа.
export const formatPrice = (value: number) => {
return `${value} ₽`;
};
```
## Работа с any/unknown
- `any` использовать только для временных заглушек.
- `unknown` сужать через проверки перед использованием.
**Хорошо**
```ts
const parse = (value: unknown): string => {
if (typeof value === 'string') {
return value;
}
return '';
};
```
**Плохо**
```ts
// Плохо: any отключает проверку типов.
const parse = (value: any) => value;
```
<a name="7-project-structuremd"></a>
# Структура проекта
Раздел описывает базовую структуру проекта и принципы организации модулей на уровне папок и файлов.
## Базовая структура проекта
**Хорошо**
```text
src/
├── app/ # Инициализация приложения, роутинг, провайдеры
│ ├── config/ # Конфигурации и константы уровня приложения
│ ├── providers/ # Провайдеры и обёртки приложения
│ ├── routing/ # Конфигурация маршрутов
│ └── index.ts # Публичный API слоя
├── screens/ # Экраны приложения
│ └── Profile/ # Экран профиля
│ └── ... # ui/model/index.ts
├── layouts/ # Общие шаблоны и каркасы страниц
│ └── MainLayout/ # Основной layout
│ └── ... # ui/index.ts
├── widgets/ # Крупные блоки интерфейса
│ └── Header/ # Виджет шапки
│ └── ... # ui/index.ts
├── features/ # Пользовательские сценарии
│ └── auth-by-email/ # Авторизация по email
│ └── ... # ui/model/api/index.ts
├── entities/ # Бизнес-сущности
│ └── user/ # Сущность пользователя
│ └── ... # ui/model/api/lib/index.ts
└── shared/ # Общие ресурсы проекта
├── ui/ # Базовые UI-компоненты
├── lib/ # Утилиты и хелперы
├── services/ # Общие сервисы и клиенты
├── config/ # Общие конфигурации
├── styles/ # Глобальные стили и токены
└── assets/ # Ресурсы
├── images/ # Изображения
├── icons/ # Иконки
├── fonts/ # Шрифты
└── video/ # Видео
```
**Плохо**
```text
// Плохо: слои смешаны, нет понятных границ и публичного API.
src/
├── components/
├── api/
├── styles/
└── user.ts
```
## Правила организации
- Каждый слой и модуль хранится в собственной папке.
- Внутренние реализации разделяются на `ui`, `model`, `lib`, `api`.
- Публичный API модуля объявляется в `index.ts`.
- Внутренние файлы не импортируются напрямую извне.
- Не смешивать ответственность разных слоёв в одном модуле.
<a name="8-componentsmd"></a>
<a name="9-stylesmd"></a>
<a name="10-images-spritesmd"></a>
<a name="11-videomd"></a>
<a name="12-apimd"></a>
<a name="13-storesmd"></a>
<a name="14-hooksmd"></a>
<a name="15-fontsmd"></a>
<a name="16-localizationmd"></a>