25 KiB
Технологии и библиотеки
Базовый стек технологий и библиотек, на который опираются проекты и примеры в документации.
Что используем обычно
- TypeScript — типизация всей логики и компонентов.
- FSD (Feature-Sliced Design) — структура проекта и границы модулей.
- React + Next.js — основной стек для UI и приложения.
- Mantine UI — базовые UI-компоненты.
- SWR — получение данных по GET (кеширование и revalidate).
- Zustand — глобальное состояние.
- i18next (i18n) — локализация всех пользовательских текстов.
- Vitest — тестирование.
- clsx — конкатенация CSS‑классов.
- PostCSS Modules — изоляция стилей.
- Mobile First — подход к адаптивной верстке.
Архитектура
Архитектура построена на FSD (Feature‑Sliced Design) и строгих границах модулей. Цель — разделить ответственность, упростить сопровождение и контроль зависимостей.
Принципы
- Разделять UI, бизнес-логику и инфраструктуру.
- Держать зависимости однонаправленными.
- Открывать наружу только публичный API модулей.
- Не допускать циклических зависимостей.
Слои
- app — инициализация приложения, роутинг, конфигурации, глобальные провайдеры.
- screens — экраны и их композиция.
- layouts — каркас и шаблоны страниц.
- widgets — крупные блоки интерфейса, собирающие несколько сценариев.
- features — отдельные пользовательские действия и сценарии.
- entities — бизнес-сущности и их модель.
- shared — переиспользуемая инфраструктура, утилиты и базовые UI‑компоненты.
Правила зависимостей
- Допустимые импорты идут сверху вниз:
app → screens → layouts → widgets → features → entities → shared. - Импорты между слоями — через публичный API.
- Внутри одного слоя — относительные импорты.
Публичный API модулей
- Каждый модуль экспортирует наружу только то, что нужно другим слоям.
- Внешние импорты идут только через
index‑файл модуля. - Внутренние файлы не импортируются напрямую извне.
Границы ответственности
- Бизнес‑логика не размещается в UI‑компонентах.
- UI‑компоненты должны быть максимально простыми и предсказуемыми.
- Связь между независимыми сценариями поднимается на уровень выше.
Типовые ошибки
- Импорт из более высокого слоя в более низкий.
- Смешивание логики нескольких слоёв в одном модуле.
- Прямые импорты внутренних файлов, минуя публичный API.
Стиль кода
Раздел описывает единые правила оформления кода: отступы, переносы, кавычки, порядок импортов и базовую читаемость.
Отступы
- 2 пробела (не табы).
Длина строк
- Ориентироваться на 100 символов, но превышение допустимо, если строка читается легко.
- Переносить выражение на новые строки, когда строка становится плохо читаемой.
- Не переносить строку внутри строковых литералов без необходимости.
Хорошо
const config = createRequestConfig(
endpoint,
{
headers: {
'X-Request-Id': requestId,
'X-User-Id': userId,
},
params: {
page,
pageSize,
sort: 'createdAt',
},
},
timeoutMs,
);
Плохо
// Плохо: длинная строка с вложенными структурами плохо читается.
const config = createRequestConfig(endpoint, { headers: { 'X-Request-Id': requestId, 'X-User-Id': userId }, params: { page, pageSize, sort: 'createdAt' } }, timeoutMs);
Кавычки
- В JavaScript/TypeScript использовать одинарные кавычки.
- В JSX/TSX для атрибутов использовать двойные кавычки.
- Шаблонные строки использовать только при интерполяции или многострочном тексте.
Хорошо
const label = 'Сохранить';
const title = `Привет, ${name}`;
<input type="text" placeholder="Введите имя" />
Плохо
// Плохо: двойные кавычки в TS и конкатенация вместо шаблонной строки.
const label = "Сохранить";
const title = 'Привет, ' + name;
// Плохо: одинарные кавычки в JSX-атрибутах.
<input type='text' placeholder='Введите имя' />
Точки с запятой и запятые
- Допускаются упущения точки с запятой, если код остаётся читаемым и однозначным.
- В многострочных массивах, объектах и параметрах функции запятая в конце допускается, но не обязательна.
Импорты
- В именованных импортах использовать пробелы внутри фигурных скобок.
- Типы импортировать через
import type. defaultимпорт и экспорт избегать, использовать именованные.- Избегать импорта всего модуля через
*.
Хорошо
import { MyComponent } from 'MyComponent';
import type { User } from '../model/types';
Плохо
// Плохо: default импорт и отсутствие пробелов в именованном импорте.
import MyComponent from 'MyComponent';
import type {User} from '../model/types';
Ранние возвраты (early return)
- Использовать ранние возвраты для упрощения чтения.
- Избегать
elseпослеreturn.
Хорошо
const getName = (user?: { name: string }) => {
if (!user) {
return 'Гость';
}
return user.name;
};
Плохо
// Плохо: лишний else после return усложняет чтение.
const getName = (user?: { name: string }) => {
if (user) {
return user.name;
} else {
return 'Гость';
}
};
Форматирование объектов и массивов
- В многострочных объектах каждое свойство на новой строке.
- В многострочных массивах каждый элемент на новой строке.
- Объекты и массивы можно писать в одну строку, если длина строки не превышает 100 символов.
- В однострочных объектах и массивах использовать пробелы после запятых.
Хорошо
const roles = ['admin', 'editor', 'viewer'];
const options = { id: 1, name: 'User' };
const config = {
url: '/api/users',
method: 'GET',
params: { page: 1, pageSize: 20 },
};
Плохо
// Плохо: нет пробелов после запятых и объект слишком длинный для одной строки.
const roles = ['admin','editor','viewer'];
const options = { id: 1,name: 'User' };
const config = { url: '/api/users', method: 'GET', params: { page: 1, pageSize: 20 } };
Именование
Именование должно быть предсказуемым, коротким и отражать смысл сущности.
Базовые правила
| Что | Рекомендуется |
|---|---|
| Папки | 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
Хорошо
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
Плохо
// Плохо: нет единых правил для слоёв и публичных файлов.
src/
├── screens/
│ └── Main/
│ └── Main.tsx
└── features/
└── authByEmail/
└── login-form.tsx
Булевы значения
- Использовать префиксы
is,has,can,should.
Хорошо
const isReady = true;
const hasAccess = false;
const canSubmit = true;
const shouldRedirect = false;
Плохо
// Плохо: неясное булево значение без префикса.
const ready = true;
const access = false;
const submit = true;
События и обработчики
- Обработчики начинать с
handle. - События и колбэки начинать с
on.
Хорошо
const handleSubmit = () => { ... };
const onSubmit = () => { ... };
Плохо
// Плохо: неочевидное назначение имени.
const submitClick = () => { ... };
Коллекции
- Для массивов использовать имена во множественном числе.
- Для словарей/мап — использовать суффиксы
ById,Map,Dict.
Хорошо
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>;
Плохо
// Плохо: имя не отражает, что это коллекция.
const user = [];
// Плохо: словарь назван как массив.
const usersMap = [];
// Плохо: по имени непонятно, что это словарь.
const users = {} as Record<string, User>;
Документирование
Документирование должно помогать понять назначение сущности, а не дублировать её типы или очевидные детали.
Правила
- Документировать только назначение функций, компонентов, типов, интерфейсов и enum.
- Не документировать параметры, возвращаемые значения, типы пропсов и очевидные детали.
- В интерфейсах, типах и enum описывать только смысл поля или значения.
- Описание должно быть кратким, информативным и завершаться точкой.
Примеры
Хорошо
/**
* Список задач пользователя.
*/
export const TodoList = memo(() => { ... });
/**
* Интерфейс задачи.
*/
export interface TodoItem {
/** Уникальный идентификатор задачи. */
id: string;
/** Текст задачи. */
text: string;
/** Статус выполнения задачи. */
completed: boolean;
}
/**
* Перечисление фильтров задач.
*/
export enum TodoFilter {
/** Все задачи. */
All = 'all',
/** Только активные задачи. */
Active = 'active',
/** Только выполненные задачи. */
Completed = 'completed',
}
Плохо
// Плохо: дублирование параметров и возвращаемых значений.
/**
* @param id - идентификатор задачи
* @returns объект задачи
*/
// Плохо: описание очевидных деталей.
/**
* id — идентификатор задачи
* text — текст задачи
* completed — статус выполнения
*/
Типизация
Типизация обязательна для всех публичных интерфейсов, функций и компонентов. Цель — предсказуемость, безопасность и автодополнение.
Общие правила
- Указывать типы для параметров компонентов, возвращаемых значений и параметров функций.
- Предпочитать
typeдля описания сущностей иinterfaceдля расширяемых контрактов. - Избегать
anyиunknownбез необходимости. - Не использовать
ts-ignore, кроме крайних случаев с явным комментарием причины.
Типы для компонентов
- Типизировать параметры и публичный интерфейс компонента.
- Дефолтные значения описывать явно в коде.
Хорошо
/**
* Параметры кнопки.
*/
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>
)
}
Плохо
// Плохо: параметры не типизированы.
export const Button = (props) => (
<button type="button" onClick={props.onClick}>
{props.label}
</button>
);
Функции
- Для публичных функций указывать возвращаемый тип.
- Не полагаться на неявный вывод для важных API.
Хорошо
export const formatPrice = (value: number): string => {
return `${value} ₽`;
};
Плохо
// Плохо: нет явного возвращаемого типа.
export const formatPrice = (value: number) => {
return `${value} ₽`;
};
Работа с any/unknown
anyиспользовать только для временных заглушек.unknownсужать через проверки перед использованием.
Хорошо
const parse = (value: unknown): string => {
if (typeof value === 'string') {
return value;
}
return '';
};
Плохо
// Плохо: any отключает проверку типов.
const parse = (value: any) => value;
Структура проекта
Раздел описывает базовую структуру проекта и принципы организации модулей на уровне папок и файлов.
Базовая структура проекта
Хорошо
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/ # Видео
Плохо
// Плохо: слои смешаны, нет понятных границ и публичного API.
src/
├── components/
├── api/
├── styles/
└── user.ts
Правила организации
- Каждый слой и модуль хранится в собственной папке.
- Внутренние реализации разделяются на
ui,model,lib,api. - Публичный API модуля объявляется в
index.ts. - Внутренние файлы не импортируются напрямую извне.
- Не смешивать ответственность разных слоёв в одном модуле.
Компоненты
Раздел описывает правила создания UI‑компонентов. Эти правила обязательны для всех слоёв FSD: app, screens, layouts, widgets, features, entities, shared.
Базовая структура компонента
Минимальный набор файлов: компонент, стили, типы и публичный экспорт.
container/
├── styles/
│ └── container.module.scss
├── types/
│ └── container.interface.ts
├── container.ui.tsx
└── index.ts
Пример базового компонента
styles/container.module.scss
.root {}
В CSS Modules использование имени класса .root — это общепринятое соглашение (best practice)
types/container.interface.ts
import type { HTMLAttributes } from 'react'
/**
* Параметры контейнера.
*/
export interface ContainerProps extends HTMLAttributes<HTMLDivElement> {}
Интерфес параметров компонента всегда наследует свойства своего тега: div, button, итд..
container.ui.tsx
import type { FC } from 'react'
import { cl } from 'clsx'
import type { ContainerProps } from './types/container.interface'
import styles from './styles/container.module.scss'
/**
* Контейнер с адаптивной максимальной шириной.
*
* Используется для:
* - ограничения ширины контента
* - центрирования содержимого
* - построения адаптивной сетки страницы
*/
export const Container: FC<ContainerProps> = ({ className, ...htmlAttr }) => {
return (
<div {...htmlAttr} className={cl(styles.root, className)}>
Container...
</div>
)
}
- Компонент объявляется через
constи экспортируется именованно. - Пропсы деструктурируются в сигнатуре; если их больше двух — деструктуризацию переносим в тело компонента.
- Из пропсов отдельно извлекаются
classNameи...htmlAttr, чтобы корректно объединять классы и прокидывать остальные атрибуты. cl— короткое имя функции для конкатенации CSS‑классов.FC<>используется для декларацииchildren.
index.ts
export { Container } from './container.ui'
Вложенные (дочерние) компоненты
Если для реализации функционала нужны компоненты, которые используются только внутри текущего компонента, создавайте их как вложенные в папке ui/. Такие компоненты не экспортируются наружу и используются только локально.
Вложенные компоненты подчиняются тем же правилам по структуре, именованию и стилю (включая папку styles/ для их стилей).