Перейти к основному содержанию

Лучшие практики

Этот документ содержит общие рекомендации и указания по эффективному использованию библиотеки компонентов @ui8kit/core. Он охватывает стратегии выбора компонентов, использование системы вариантов, практики семантического HTML, доступность, оптимизацию производительности и паттерны композиции. Для общих паттернов разработки и типичных примеров использования см. Базовый рабочий процесс. Для обработки граничных случаев, требующих пользовательских решений, см. Продвинутый рабочий процесс.

Стратегия выбора компонентов

Выбор правильного уровня компонента является основой для написания поддерживаемого кода. Трёхслойная архитектура обеспечивает чёткие границы для различных уровней абстракции.

Схема принятия решений для выбора компонентов

Да

Нет

Да

Нет

Да

Нет

Grid макет

Flex макет

Стек элементов

Универсальный

Необходимо отрисовать
UI элемент

Создание структуры
страницы или
дашборда?

Нужен готовый
компонент с
вариантами?

Нужен семантический
HTML элемент?

Нужен контроль
макета?

Использовать DashLayout
src/layouts/DashLayout.tsx

Использовать LayoutBlock
src/layouts/LayoutBlock.tsx

Использовать SplitBlock
src/layouts/SplitBlock.tsx

Использовать UI компонент
Button, Card, Badge и т.д.
src/components/ui/

Использовать Block
с пропом component
src/core/ui/Block.tsx

Использовать Box
универсальный контейнер
src/core/ui/Box.tsx

Использовать Grid
src/core/ui/Grid.tsx

Использовать Flex
src/core/ui/Flex.tsx

Использовать Stack
src/core/ui/Stack.tsx
Источники: README.md64-89 README.md105-120 README.md147-168 src/components/README.md1-19

Использование Уровня 1: Базовые примитивы

Используйте базовые примитивы, когда вам нужен максимальный контроль над макетом и семантической структурой:
КомпонентКогда использоватьПример сценария
BlockНужен семантический HTML5 элемент с вариантами<Block component="section">, <Block component="nav">
BoxНужен универсальный контейнер без семантического значенияОбёрточные div’ы, flex элементы, ячейки сетки
GridНужен CSS Grid макетСетки товаров, галереи изображений, макеты карточек
FlexНужен flexbox макет с явным контролемПользовательские панели инструментов, сложное выравнивание
StackНужна вертикальная/горизонтальная укладка с промежуткомПоля форм, группы кнопок, секции контента
Источники: README.md81-103 src/components/README.md23-92

Использование Уровня 2: UI компоненты

Используйте UI компоненты для готовых, предварительно стилизованных элементов:
// ✅ Хорошо: Использование UI компонента для обычных элементов
<Button variant="primary" size="lg" p="md" rounded="lg">
  Submit
</Button>

// ❌ Плохо: Воссоздание UI компонента с примитивами
<Box 
  component="button" 
  className="inline-flex items-center justify-center bg-primary text-primary-foreground..."
>
  Submit
</Box>
Источники: README.md105-145 src/components/README.md94-204

Использование Уровня 3: Компоненты макетов

Используйте компоненты макетов для структурной организации страницы:
// ✅ Хорошо: DashLayout для структуры дашборда
<DashLayout 
  sidebar={<NavigationSidebar />}
  header={<DashboardHeader />}
>
  <Container>
    <Card p="lg">Content</Card>
  </Container>
</DashLayout>

// ❌ Плохо: Ручная реконструкция макета
<Box className="flex min-h-screen">
  <Box className="w-64 border-r">
    <NavigationSidebar />
  </Box>
  {/* Сложная ручная логика макета */}
</Box>
Источники: README.md147-168 src/layouts/DashLayout.tsx1-99

Использование системы вариантов

Система вариантов устраняет ~80% использования className через 12 композируемых вариантов. Следуйте этим рекомендациям, чтобы максимизировать её эффективность.

Поток применения вариантов

Разработчик пишет JSX

Пропы вариантов
p='lg'
rounded='xl'
shadow='md'
bg='card'

class-variance-authority
Преобразует варианты в классы

core-classes.json
618 валидированных классов

Tailwind CSS
Применяет утилитарные классы

Отрисованный DOM
с семантическими элементами
Источники: README.md170-217 src/lib/core-classes.json1-619 scripts/cva-extractor.ts223-260

Рекомендации по композиции вариантов

Категория вариантовПропыЗначенияСлучай использования
Spacingp, px, py, pt, pb, pl, pr, m, mx, my, mt, mb, ml, mrnone, xs, sm, md, lg, xl, 2xl, autoКонтроль отступов и полей
Colorsbg, c, borderColorЦвета дизайн-системыЦвета фона, текста, границ
Layoutw, h, minH, maxW, positionauto, full, screen, fit, min, maxКонтроль размеров и позиционирования
Typographysize, weight, align, leadingЗначения для шрифтовСтилизация текста
Effectsrounded, shadow, borderПредустановленные значенияВизуальные улучшения
// ✅ Хорошо: Композиция вариантов для сложной стилизации
<Card 
  p="xl" 
  rounded="2xl" 
  shadow="lg"
  bg="card"
  border="default"
  w="full"
  maxW="2xl"
  mx="auto"
>
  Content
</Card>

// ❌ Плохо: Использование className для стилей, покрываемых вариантами
<Card className="p-12 rounded-2xl shadow-lg bg-card border w-full max-w-2xl mx-auto">
  Content
</Card>

// ⚠️ Допустимо: className для 20% граничных случаев
<Card 
  p="xl" 
  rounded="2xl"
  className="animate-pulse hover:scale-105 transition-transform"
>
  Пользовательская анимация
</Card>
Источники: README.md171-217 src/components/README.md206-222

Когда использовать className

Система вариантов покрывает ~80% потребностей в стилизации. Используйте className для:
  1. Анимаций и переходов (не покрывается вариантами)
  2. Псевдоклассов таких как :hover, :focus, :active
  3. Пользовательских grid/flex паттернов за пределами базовых макетов
  4. Специфичных для проекта утилитарных классов, не входящих в дизайн-систему
// ✅ Хорошо: Использование className для анимаций
<Box 
  p="lg" 
  rounded="xl"
  className="hover:scale-105 transition-transform duration-200"
>
  Интерактивный элемент
</Box>

// ✅ Хорошо: Использование className для псевдоклассов
<Button 
  variant="primary" 
  size="md"
  className="focus-visible:ring-2 focus-visible:ring-offset-2"
>
  Доступная кнопка
</Button>
Источники: README.md278-304 src/components/README.md237-244

Лучшие практики семантического HTML

Библиотека делает акцент на семантических элементах HTML5 для доступности и SEO. Всегда используйте наиболее подходящий семантический элемент.

Выбор семантического элемента

Да

Нет

Да

Нет

Да

Нет

Да

Нет

Да

Нет

Да

Нет

Да

Нет

Рендеринг контента

Меню навигации
или ссылки?

Самодостаточный
синдицированный контент?

Тематическая
группировка?

Косвенно связанный
контент?

Вводный
контент?

Контент футера?

Основная область
контента?

Block component='nav'

Block component='article'

Block component='section'

Block component='aside'

Block component='header'

Block component='footer'

Block component='main'

Box
универсальный контейнер
Источники: README.md14 src/components/README.md239 src/core/ui/Block.tsx1-15

Примеры семантического HTML

// ✅ Хорошо: Семантическая структура страницы
<Block component="main" py="xl">
  <Block component="section" py="lg">
    <Container>
      <Block component="header" mb="lg">
        <Title order={1}>Заголовок страницы</Title>
      </Block>
      <Block component="article">
        <Text>Контент статьи...</Text>
      </Block>
    </Container>
  </Block>
</Block>

// ❌ Плохо: Универсальные div'ы повсюду
<Box py="xl">
  <Box py="lg">
    <Container>
      <Box mb="lg">
        <Title order={1}>Заголовок страницы</Title>
      </Box>
      <Box>
        <Text>Контент статьи...</Text>
      </Box>
    </Container>
  </Box>
</Box>

// ✅ Хорошо: Навигация с семантическим nav элементом
<Block component="nav" p="md" bg="background" border="default">
  <Stack gap="sm">
    <a href="/">Главная</a>
    <a href="/about">О нас</a>
  </Stack>
</Block>

// ✅ Хорошо: Правильная иерархия заголовков
<Block component="article">
  <Title order={1}>Главный заголовок</Title>
  <Title order={2}>Заголовок секции</Title>
  <Title order={3}>Заголовок подсекции</Title>
</Block>
Источники: README.md14 src/components/README.md28-64 src/core/ui/Block.tsx1-15

Лучшие практики доступности

Убедитесь, что все компоненты доступны для пользователей с ограниченными возможностями, следуя рекомендациям WCAG.

Основные принципы доступности

ПринципРеализацияПример
Навигация с клавиатурыОбеспечьте доступность всех интерактивных элементов с клавиатурыИспользуйте семантический <button> вместо <div onClick>
ARIA меткиПредоставьте метки для программ чтения с экранаaria-label, aria-labelledby, aria-describedby
Управление фокусомВидимые индикаторы фокусаСтили :focus-visible
Цветовой контрастМинимальное соотношение 4.5:1 для текстаИспользуйте цвета дизайн-системы
Альтернативный текстОписательный текст для изображенийВсегда предоставляйте проп alt
// ✅ Хорошо: Доступная кнопка
<Button 
  variant="primary" 
  size="md"
  aria-label="Отправить форму"
  className="focus-visible:ring-2 focus-visible:ring-primary"
>
  Отправить
</Button>

// ✅ Хорошо: Доступное изображение
<Image 
  src="/product.jpg"
  alt="Синие кроссовки с белыми полосками"
  rounded="lg"
/>

// ✅ Хорошо: Доступная навигация
<Block component="nav" aria-label="Основная навигация">
  <Stack gap="md" role="list">
    <a href="/" role="listitem">Главная</a>
    <a href="/about" role="listitem">О нас</a>
  </Stack>
</Block>

// ❌ Плохо: Несемантический интерактивный элемент
<Box 
  onClick={handleClick}
  className="cursor-pointer"
>
  Нажми меня
</Box>

// ✅ Хорошо: Использование компонента Button
<Button onClick={handleClick}>
  Нажми меня
</Button>
Источники: README.md14 src/components/ui/Button.tsx1-40 src/components/ui/Image.tsx1-20

Доступность форм

// ✅ Хорошо: Доступная форма с семантической структурой
<Block component="form" onSubmit={handleSubmit}>
  <Stack gap="md">
    <Box>
      <Text as="label" htmlFor="email" weight="medium" mb="xs">
        Email
      </Text>
      <Box
        component="input"
        id="email"
        type="email"
        aria-required="true"
        aria-describedby="email-error"
        p="sm"
        rounded="md"
        border="default"
        w="full"
      />
      <Text id="email-error" size="sm" c="destructive" mt="xs">
        {emailError}
      </Text>
    </Box>
    
    <Button type="submit" variant="primary">
      Отправить
    </Button>
  </Stack>
</Block>
Источники: src/components/GUIDE_CREATE_FORM.md1-50 src/components/README.md239

Оптимизация производительности

Следуйте этим паттернам для поддержания оптимальной производительности в больших приложениях.

Композиция компонентов против передачи пропов

// ✅ Хорошо: Естественная композиция компонентов
<Card p="lg" rounded="xl">
  <Card.Header>
    <Title order={2}>Товар</Title>
  </Card.Header>
  <Card.Content>
    <Stack gap="md">
      <Image src="/product.jpg" alt="Товар" />
      <Text>Описание</Text>
    </Stack>
  </Card.Content>
  <Card.Footer>
    <Button variant="primary">Купить сейчас</Button>
  </Card.Footer>
</Card>

// ❌ Плохо: Избыточная передача пропов
<ProductCard
  title="Товар"
  imageSrc="/product.jpg"
  imageAlt="Товар"
  description="Описание"
  buttonText="Купить сейчас"
  buttonVariant="primary"
  cardPadding="lg"
  cardRounded="xl"
/>
Источники: README.md306-338 src/components/ui/Card.tsx1-80

Рекомендации по мемоизации

import { memo } from 'react';

// ✅ Хорошо: Мемоизация дорогих элементов списка
const ProductCard = memo(({ product }) => (
  <Card p="md" rounded="lg">
    <Card.Content>
      <Stack gap="sm">
        <Image src={product.image} alt={product.name} />
        <Text>{product.name}</Text>
        <Text size="sm" c="muted-foreground">
          ${product.price}
        </Text>
      </Stack>
    </Card.Content>
  </Card>
));

// ✅ Хорошо: Использование forwardRef для передачи ref
import { forwardRef } from 'react';

const CustomInput = forwardRef((props, ref) => (
  <Box
    component="input"
    ref={ref}
    p="sm"
    rounded="md"
    border="default"
    {...props}
  />
));
Источники: src/core/ui/Box.tsx1-30 src/components/ui/Button.tsx1-40

Избегайте ненужных повторных рендеров

// ✅ Хорошо: Использование примитивных значений в качестве зависимостей
const [count, setCount] = useState(0);

useEffect(() => {
  // Логика эффекта
}, [count]);

// ❌ Плохо: Зависимости-объекты/массивы вызывают ненужные повторные рендеры
const config = { theme: 'dark' };

useEffect(() => {
  // Это выполняется при каждом рендере
}, [config]);

// ✅ Хорошо: Извлечение примитивных значений
const theme = config.theme;

useEffect(() => {
  // Это выполняется только при изменении theme
}, [theme]);
Источники: README.md341-359

Паттерны композиции

Библиотека поддерживает несколько паттернов композиции. Выберите подходящий паттерн для вашего случая использования.

Сравнение паттернов

Паттерн 4: Композиция через Children

Stack

Дочерний компонент 1

Дочерний компонент 2

Дочерний компонент 3

Паттерн 3: Хуки контента

LayoutBlock

функция beforeContent

функция content

функция afterContent

Паттерн 2: Передача пропов

Button
расширяет BaseButton

spacingVariants
roundedVariants
shadowVariants

Паттерн 1: Составные компоненты

Card

Card.Header

Card.Content

Card.Footer
Источники: README.md15-17 src/layouts/LayoutBlock.tsx15-22 src/components/ui/Card.tsx1-80

Паттерн составных компонентов

Используйте для гибких структур компонентов с предопределёнными подкомпонентами:
// ✅ Хорошо: Гибкая композиция карточки
<Card p="lg" rounded="xl" shadow="md">
  <Card.Header>
    <Card.Title>Заголовок</Card.Title>
    <Card.Description>Описание</Card.Description>
  </Card.Header>
  <Card.Content>
    <Stack gap="md">
      {/* Контент */}
    </Stack>
  </Card.Content>
  <Card.Footer>
    <Group gap="md" justify="end">
      <Button variant="secondary">Отмена</Button>
      <Button variant="primary">Сохранить</Button>
    </Group>
  </Card.Footer>
</Card>

// ✅ Хорошо: Опциональные части
<Card p="lg">
  <Card.Content>
    {/* Только контент, без header/footer */}
  </Card.Content>
</Card>
Источники: README.md38-59 README.md121-145 src/components/ui/Card.tsx1-80

Паттерн передачи пропов

Все UI компоненты передают пропы вариантов от базовых компонентов:
// ✅ Хорошо: Button с переданными вариантами отступов и визуальными вариантами
<Button 
  variant="primary"
  size="lg"
  p="md"        // Из spacingVariants
  rounded="lg"  // Из roundedVariants
  shadow="sm"   // Из shadowVariants
>
  Нажми меня
</Button>

// ✅ Хорошо: Card с переданными вариантами макета
<Card
  p="xl"         // Из spacingVariants
  rounded="2xl"  // Из roundedVariants
  shadow="lg"    // Из shadowVariants
  w="full"       // Из layoutVariants
  maxW="2xl"     // Из layoutVariants
  mx="auto"      // Из spacingVariants
>
  Контент
</Card>
Источники: README.md16 src/components/README.md12-18 src/components/ui/Button.tsx1-40

Паттерн хуков контента

Используйте для динамического рендеринга в компонентах макетов:
// ✅ Хорошо: LayoutBlock с хуками контента
<LayoutBlock
  layout="grid"
  cols={3}
  gap="lg"
  contentHooks={{
    beforeContent: () => (
      <Block component="header" mb="lg">
        <Title order={1}>Заголовок страницы</Title>
      </Block>
    ),
    content: () => products.map(product => (
      <Card key={product.id} p="md">
        {/* Карточка товара */}
      </Card>
    )),
    afterContent: () => (
      <Block component="footer" mt="lg">
        <Button>Загрузить ещё</Button>
      </Block>
    )
  }}
/>
Источники: src/layouts/LayoutBlock.tsx15-22 src/layouts/LayoutBlock.tsx1-50

Распространённые антипаттерны

Избегайте этих распространённых ошибок при использовании библиотеки.

Антипаттерн 1: Чрезмерное использование className

// ❌ Плохо: Использование className для стилей, покрываемых вариантами
<Card className="p-12 rounded-2xl shadow-lg bg-card w-full max-w-2xl">
  Контент
</Card>

// ✅ Хорошо: Использование вариантов
<Card p="xl" rounded="2xl" shadow="lg" bg="card" w="full" maxW="2xl">
  Контент
</Card>

Антипаттерн 2: Обход семантического HTML

// ❌ Плохо: Универсальные div'ы для навигации
<Box className="nav-container">
  <Box className="nav-item">Главная</Box>
  <Box className="nav-item">О нас</Box>
</Box>

// ✅ Хорошо: Семантическая навигация
<Block component="nav" aria-label="Основная навигация">
  <Stack gap="md" component="ul" role="list">
    <li><a href="/">Главная</a></li>
    <li><a href="/about">О нас</a></li>
  </Stack>
</Block>

Антипаттерн 3: Воссоздание существующих компонентов

// ❌ Плохо: Воссоздание компонента Button
const CustomButton = ({ children, ...props }) => (
  <Box
    component="button"
    className="inline-flex items-center justify-center rounded-md px-4 py-2 bg-primary text-white"
    {...props}
  >
    {children}
  </Box>
);

// ✅ Хорошо: Расширение существующего Button
import { Button } from '@ui8kit/core';

const CustomButton = ({ children, ...props }) => (
  <Button 
    variant="primary" 
    size="md"
    className="custom-animation" // Добавляем только пользовательские стили
    {...props}
  >
    {children}
  </Button>
);

Антипаттерн 4: Избыточная вложенность

// ❌ Плохо: Ненужная вложенность обёрток
<Box>
  <Box>
    <Box>
      <Box>
        <Text>Контент</Text>
      </Box>
    </Box>
  </Box>
</Box>

// ✅ Хорошо: Минимальная вложенность
<Box>
  <Text>Контент</Text>
</Box>

// ✅ Хорошо: Использование Stack для отступов вместо обёрток
<Stack gap="md">
  <Text>Элемент 1</Text>
  <Text>Элемент 2</Text>
  <Text>Элемент 3</Text>
</Stack>

Антипаттерн 5: Игнорирование data-class атрибутов

// ❌ Плохо: Пользовательские селекторы без data-class
<Card className="my-custom-card">
  <div className="my-custom-header">Заголовок</div>
</Card>

// ✅ Хорошо: Использование data-class атрибутов для таргетинга
<Card data-class="product-card">
  <Card.Header data-class="product-card-header">
    Заголовок
  </Card.Header>
</Card>

// В CSS или тестировании:
// [data-class="product-card"] { ... }
// screen.getByTestId('product-card')
Источники: src/components/README.md223-235 src/components/README.md237-244

Итоговый контрольный список

Используйте этот контрольный список при создании интерфейсов с @ui8kit/core:
  • Выбор компонентов: Используйте соответствующий уровень (Core/UI/Layout) для каждого случая использования
  • Система вариантов: Предпочитайте варианты вместо className для ~80% потребностей в стилизации
  • Семантический HTML: Используйте подходящие семантические элементы через проп component в Block
  • Доступность: Включайте ARIA метки, навигацию с клавиатуры и управление фокусом
  • Производительность: Композируйте компоненты естественно, избегайте передачи пропов
  • Композиция: Используйте составные компоненты, передачу пропов и хуки контента соответствующим образом
  • Data атрибуты: Используйте data-class атрибуты для таргетинга DOM и тестирования
  • Типобезопасность: Используйте TypeScript типы для валидации пропов
  • Избегайте антипаттернов: Не злоупотребляйте className, не обходите семантику и не воссоздавайте компоненты
Источники: README.md388-402 src/components/README.md237-244