Структура и реализация компонента (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 и сборщика.