Структура и реализация компонента (references/component-structure.md)
Источник:
ds-skills/core-ui-development-skills/src/skills/build-core-ui-components/references/component-structure.md— справочник скилла build-core-ui-components
Базовая структура
src/my-widget/
├── my-widget.tsx
├── types.ts
├── constants.ts
├── index.ts
├── styles.module.scss
├── my-widget.test.tsx
├── __snapshots__/
├── components/ # при необходимости
├── utils/ # при необходимости
└── storybook/
├── stories.tsx
├── docs.mdx
├── constants.ts(x) # при необходимости
├── styles.module.scss # при необходимости
└── examples/ # при необходимости
Создавать только необходимые файлы. Следовать расширению index.ts или index.tsx, принятому у ближайшего актуального аналога.
Экспортировать публичный компонент и типы из локального index, затем добавить реэкспорт в src/index.ts:
export { MyWidget } from "./my-widget";
export type { MyWidgetProps } from "./types";
TypeScript API
- Предпочитать
type, если ближайшие компоненты не требуют другого подхода. - Называть пропсы
MyWidgetProps; обработчики — по семантике соседних компонентов. - Получать строковые union-типы из словарей
as constчерезTValuesизsrc/common/types, когда нужны варианты. - Документировать публичные пропсы JSDoc на русском. Для необязательного пропса с дефолтом указывать
@default. - Расширять нативные атрибуты только когда компонент должен их принимать; исключать конфликты через
Omit. - Выбирать DOM-элемент по семантике. Не добавлять состояния, которых нет в запросе или дизайне.
export const MY_WIDGET_TYPES = {
PRIMARY: "primary",
SECONDARY: "secondary",
} as const;
export const DATA_TEST_ID = "my-widget";
Называть текстовые константы по роли элемента (SEND_BUTTON_TEXT, AMOUNT_INPUT_LABEL), а не буквальному переводу.
React
- Использовать функциональный компонент. Применять
forwardRefтолько для реальной потребности в DOM или imperative API. - Задавать дефолты в деструктуризации, включая
dataTestId = DATA_TEST_ID, если этот проп поддерживается. - Передавать поддерживаемые нативные и
aria-*атрибуты на корневой интерактивный элемент. - Собирать классы через
classnames, сохраняя пользовательскийclassName. - Использовать
useMemoizedFnтолько при важной стабильности ссылки или по паттерну ближайшего аналога. - Не добавлять мемоизацию, состояние и эффекты без причины.
- Ставить
data-testidна корневой элемент; дочерние идентификаторы строить как${dataTestId}-.... - Переиспользовать подходящие
IconContainer, компоненты текста,Typography,mergeRefsи другие локальные примитивы. - Группировать импорты согласно ESLint и соседним файлам.
Импорты через aliases
Перед использованием alias проверить конфигурацию текущего репозитория:
compilerOptions.paths и baseUrl в tsconfig, а также runtime aliases в
Vitest, Storybook и сборщике, если эти среды используются. Не переносить aliases
из core-ui в другой репозиторий автоматически.
В подтверждённом репозитории core-ui для импортов из других директорий
использовать настроенный alias src/*, а не подниматься по дереву через
../...
// Корректно
import { CustomSkeletonStoryProps } from "src/custom-layer/storybook/types";
// Некорректно
import { CustomSkeletonStoryProps } from "../../types";
Относительные импорты ./... оставлять для файлов из той же локальной
директории, например ./styles.module.scss, ./constants или локального
index. Использовать существующие специальные aliases test-utils и
core-ui-storybook/* по их назначению только после проверки их наличия.
Если текущий репозиторий не является core-ui или нужный alias в нём не
настроен, следовать локальным соглашениям репозитория и использовать допустимые
относительные импорты. Не создавать новый alias только ради применения этого
правила: такое изменение требует отдельного согласования и синхронной настройки
TypeScript, тестов, Storybook и сборщика.