forked from templates/nextjs-template
style: Обновлены правила код стайла
This commit is contained in:
@@ -1,201 +1,165 @@
|
||||
---
|
||||
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` файл внутри модуля;
|
||||
- `module` — папка с `index.ts`, сегментами и собственной публичной границей.
|
||||
Если в проекте нет шаблона `.templates/component`, сначала создайте шаблон по разделу [Создание шаблонов](./templates/templates-create.md), и только потом генерируйте компонент на его основе.
|
||||
:::
|
||||
|
||||
## Создание
|
||||
|
||||
1. Проверьте, что в проекте есть шаблон `.templates/component`.
|
||||
2. Если шаблона нет — создайте его по разделу [Создание шаблонов](./templates/templates-create.md).
|
||||
3. Сгенерируйте компонент через [VS Code или CLI](./templates/templates-usage.md).
|
||||
|
||||
Структура и код ниже показывают ожидаемый результат генерации. Их нельзя использовать как инструкцию для ручного создания файлов.
|
||||
|
||||
## Структура
|
||||
|
||||
Компонент размещается в `ui/{component-name}/` родительского модуля.
|
||||
|
||||
Для каждого компонента обязательны `.tsx`, типы, стили и локальный `index.ts`.
|
||||
|
||||
```text
|
||||
user/
|
||||
├── 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/
|
||||
user-card/
|
||||
└── ui/
|
||||
└── user-avatar/
|
||||
└── user-status/
|
||||
├── styles/
|
||||
│ └── user-avatar.module.css
|
||||
├── user-avatar.tsx
|
||||
│ └── user-status.module.css
|
||||
├── types/
|
||||
│ └── user-status-props.type.ts
|
||||
├── user-status.tsx
|
||||
└── 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
|
||||
import type { ImageProps } from 'next/image'
|
||||
import type { ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры UserAvatar.
|
||||
* Параметры UserStatus.
|
||||
*/
|
||||
export type UserAvatarParams = {}
|
||||
export type UserStatusParams = {
|
||||
/** Текст статуса пользователя. */
|
||||
label: string
|
||||
/** Доступен ли пользователь сейчас. */
|
||||
isOnline: boolean
|
||||
}
|
||||
|
||||
/** Пропсы базового изображения. */
|
||||
type RootAttrs = ImageProps
|
||||
/** Атрибуты корневого элемента без children. */
|
||||
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
|
||||
import cl from 'clsx'
|
||||
import Image from 'next/image'
|
||||
import type { UserAvatarProps } from '../types/user-avatar.type'
|
||||
import styles from '../styles/user-avatar.module.css'
|
||||
import type { UserStatusProps } from './types/user-status-props.type'
|
||||
import styles from './styles/user-status.module.css'
|
||||
|
||||
/**
|
||||
* Аватар пользователя.
|
||||
* Статус пользователя в карточке профиля.
|
||||
*
|
||||
* Используется для:
|
||||
* - отображения пользователя в карточке
|
||||
* - отображения пользователя в шапке профиля
|
||||
* - отображения текущей доступности пользователя
|
||||
* - визуального выделения онлайн- и офлайн-состояний
|
||||
*/
|
||||
export const UserAvatar = (props: UserAvatarProps) => {
|
||||
const { className, ...imageProps } = props
|
||||
export const UserStatus = (props: UserStatusProps) => {
|
||||
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
|
||||
.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%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.online {
|
||||
color: var(--color-success);
|
||||
}
|
||||
```
|
||||
|
||||
## Когда нужен модуль
|
||||
### Локальный экспорт
|
||||
|
||||
Решение о выделении модуля остаётся за разработчиком. Поднимать компонент в модуль стоит, если он становится самостоятельной областью:
|
||||
`user-card/ui/user-status/index.ts`
|
||||
|
||||
- получает свои вложенные компоненты;
|
||||
- получает свои хуки, стор или сервисы;
|
||||
- получает внутренние мапперы или утилиты;
|
||||
- требует собственного публичного 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
|
||||
```ts
|
||||
export { UserStatus } from './user-status'
|
||||
export type { UserStatusProps } from './types/user-status-props.type'
|
||||
```
|
||||
|
||||
`hero-section` и `features-section` — модули, потому что это самостоятельные части страницы со своей структурой и публичной точкой входа.
|
||||
|
||||
Reference in New Issue
Block a user