202 lines
8.6 KiB
Markdown
202 lines
8.6 KiB
Markdown
|
|
---
|
|||
|
|
title: Компонент
|
|||
|
|
description: Как создавать React-компоненты внутри SLM-модулей.
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Компонент
|
|||
|
|
|
|||
|
|
Как создавать React-компоненты внутри SLM-модулей.
|
|||
|
|
|
|||
|
|
## Назначение
|
|||
|
|
|
|||
|
|
Компонент — минимальная UI-единица проекта. Это один `.tsx` файл без собственной папки, сегментов и публичного API.
|
|||
|
|
|
|||
|
|
Компонент может использовать стили, типы, хуки и другие ресурсы родительского модуля. Наличие связанных файлов в `styles/` или `types/` не превращает компонент в модуль.
|
|||
|
|
|
|||
|
|
## Компонент или модуль
|
|||
|
|
|
|||
|
|
Классификация определяется границей владения:
|
|||
|
|
|
|||
|
|
- `component` — один `.tsx` файл внутри модуля;
|
|||
|
|
- `module` — папка с `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/
|
|||
|
|
└── ui/
|
|||
|
|
└── user-avatar/
|
|||
|
|
├── styles/
|
|||
|
|
│ └── user-avatar.module.css
|
|||
|
|
├── user-avatar.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`
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
import type { ImageProps } from 'next/image'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Параметры UserAvatar.
|
|||
|
|
*/
|
|||
|
|
export type UserAvatarParams = {}
|
|||
|
|
|
|||
|
|
/** Пропсы базового изображения. */
|
|||
|
|
type RootAttrs = ImageProps
|
|||
|
|
|
|||
|
|
export type UserAvatarProps = RootAttrs & UserAvatarParams
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`user/ui/user-avatar.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'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Аватар пользователя.
|
|||
|
|
*
|
|||
|
|
* Используется для:
|
|||
|
|
* - отображения пользователя в карточке
|
|||
|
|
* - отображения пользователя в шапке профиля
|
|||
|
|
*/
|
|||
|
|
export const UserAvatar = (props: UserAvatarProps) => {
|
|||
|
|
const { className, ...imageProps } = props
|
|||
|
|
|
|||
|
|
return <Image {...imageProps} className={cl(styles.root, className)} />
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`user/styles/user-avatar.module.css`
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.root {
|
|||
|
|
display: block;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Когда нужен модуль
|
|||
|
|
|
|||
|
|
Решение о выделении модуля остаётся за разработчиком. Поднимать компонент в модуль стоит, если он становится самостоятельной областью:
|
|||
|
|
|
|||
|
|
- получает свои вложенные компоненты;
|
|||
|
|
- получает свои хуки, стор или сервисы;
|
|||
|
|
- получает внутренние мапперы или утилиты;
|
|||
|
|
- требует собственного публичного API;
|
|||
|
|
- начинает переиспользоваться вне родительского модуля;
|
|||
|
|
- становится отдельной зоной параллельной разработки.
|
|||
|
|
|
|||
|
|
Пример: страница — это screen-модуль, а самостоятельные секции страницы — вложенные модули в `parts/`.
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
screens/home/
|
|||
|
|
├── parts/
|
|||
|
|
│ ├── hero-section/
|
|||
|
|
│ │ ├── styles/
|
|||
|
|
│ │ │ └── hero-section.module.css
|
|||
|
|
│ │ ├── types/
|
|||
|
|
│ │ │ └── hero-section.type.ts
|
|||
|
|
│ │ ├── hero-section.tsx
|
|||
|
|
│ │ └── index.ts
|
|||
|
|
│ └── features-section/
|
|||
|
|
│ ├── styles/
|
|||
|
|
│ │ └── features-section.module.css
|
|||
|
|
│ ├── types/
|
|||
|
|
│ │ └── features-section.type.ts
|
|||
|
|
│ ├── features-section.tsx
|
|||
|
|
│ └── index.ts
|
|||
|
|
├── styles/
|
|||
|
|
│ └── home.module.css
|
|||
|
|
├── types/
|
|||
|
|
│ └── home.type.ts
|
|||
|
|
├── home.screen.tsx
|
|||
|
|
└── index.ts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`hero-section` и `features-section` — модули, потому что это самостоятельные части страницы со своей структурой и публичной точкой входа.
|