--- 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 } ``` `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` — модули, потому что это самостоятельные части страницы со своей структурой и публичной точкой входа.