Компонент
Как создавать React-компоненты внутри SLM-модулей.
Назначение
Компонент — минимальная UI-единица проекта. Это один .tsx файл без собственной папки, сегментов и публичного API.
Компонент может использовать стили, типы, хуки и другие ресурсы родительского модуля. Наличие связанных файлов в styles/ или types/ не превращает компонент в модуль.
Компонент или модуль
Классификация определяется границей владения:
component— один.tsxфайл внутри модуля;module— папка сindex.ts, сегментами и собственной публичной границей.
user/
├── ui/
│ └── user-avatar.tsx # компонент
├── styles/
│ └── user-avatar.module.css # ресурс родительского модуля
├── types/
│ └── user-avatar.type.ts # ресурс родительского модуля
└── user.tsx # корневой компонент модуляuser-avatar.tsx остаётся компонентом, потому что у него нет собственной папки, index.ts и сегментов.
Где размещать
Компонент размещается внутри модуля:
- В корне модуля, если это главная UI-сущность модуля.
- В
ui/, если это вспомогательный компонент модуля.
user/
├── ui/
│ └── user-avatar.tsx
├── styles/
│ ├── user.module.css
│ └── user-avatar.module.css
├── types/
│ ├── user.type.ts
│ └── user-avatar.type.ts
├── user.tsx
└── index.tsuser.tsx — корневой компонент модуля. ui/user-avatar.tsx — вспомогательный компонент этого же модуля.
Что запрещено
- Заворачивать компонент в папку:
ui/header/header.tsx. - Создавать для компонента отдельный
index.ts. - Создавать собственные сегменты внутри папки компонента:
ui/header/styles/,ui/header/types/,ui/header/hooks/и т.п. - Декларировать внутри
.tsxсторы, сервисы, API-клиенты, мапперы или утилиты. Для этого есть сегменты родительского модуля. - Размещать бизнес-правила прямо в компоненте. Компонент может использовать готовые зависимости модуля, но не определяет их.
- Размещать компонент в
parts/напрямую.parts/содержит только модули.
Плохо
user/
└── ui/
└── user-avatar/
├── styles/
│ └── user-avatar.module.css
├── user-avatar.tsx
└── index.tsХорошо
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
import type { ImageProps } from 'next/image'
/**
* Параметры UserAvatar.
*/
export type UserAvatarParams = {}
/** Пропсы базового изображения. */
type RootAttrs = ImageProps
export type UserAvatarProps = RootAttrs & UserAvatarParamsuser/ui/user-avatar.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
.root {
display: block;
border-radius: 50%;
}Когда нужен модуль
Решение о выделении модуля остаётся за разработчиком. Поднимать компонент в модуль стоит, если он становится самостоятельной областью:
- получает свои вложенные компоненты;
- получает свои хуки, стор или сервисы;
- получает внутренние мапперы или утилиты;
- требует собственного публичного API;
- начинает переиспользоваться вне родительского модуля;
- становится отдельной зоной параллельной разработки.
Пример: страница — это screen-модуль, а самостоятельные секции страницы — вложенные модули в parts/.
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.tshero-section и features-section — модули, потому что это самостоятельные части страницы со своей структурой и публичной точкой входа.