Паттерны
Полиморфный триггер
Когда использовать:
- низкоуровневый примитив-триггер должен рендериться как элемент, выбранный пользователем библиотеки
Заметки по композиции:
- гарантируйте ровно один дочерний элемент
- сохраняйте подходящий для задачи семантический элемент
Компромиссы:
- гибко
- проще ошибиться, чем при фиксированном рендере
import type { CSSProperties } from 'react';
import {
Button,
Dialog,
DialogClose,
DialogContent,
DialogTrigger,
Heading,
Icon,
IconButton,
Text,
} from '@vega-ui/react';Стилизованная ссылка-действие
Когда использовать:
- навигация должна сохранить семантику ссылки, но позаимствовать стили кнопки
Заметки по композиции:
- выбирайте элемент
<a>осознанно - сохраняйте семантику навигации явной
Компромиссы:
- сохраняет поведение ссылки
- легко злоупотребить там, где обычная ссылка была бы понятнее
import { Button, Icon } from '@vega-ui/react';
import { BookOpenIcon } from '@vega-ui/icons';
export default function StyledLinkAction() {
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-4)' }}>
<Button type='button'>Create API key</Button>
<Button asChild variant='secondary' appearance='ghost'>
<a href='/docs/api/authentication'>
<Icon size='sm'><BookOpenIcon /></Icon>
Read the API guide
</a>Написание низкоуровневых обёрток
Когда использовать:
- внутренностям дизайн-системы нужно добавить поведение в один дочерний элемент
Заметки по композиции:
- тщательно тестируйте слияние обработчиков событий
- сохраняйте семантику выбранного дочернего элемента явной
Компромиссы:
- фундамент для полиморфизма
- легко внести тонкие семантические регрессии
import { type ReactNode, useState } from 'react';
import { Button, Slot } from '@vega-ui/react';
interface TrackClickProps {
event: string
onTrack(event: string): void
children: ReactNode
}
function TrackClick({ event, onTrack, children }: TrackClickProps) {
return <Slot onClick={() => onTrack(`analytics: ${event}`)}>{children}</Slot>;
}Сравнение
Быстрое правило выбора
Используйте Slot, только когда строите низкоуровневую полиморфную композицию. В обычном продуктовом коде предпочитайте высокоуровневые API asChild.
Slot или asChild у компонентов
- Используйте
Slot, когда пишете примитивы или обёртки. - Используйте
asChildу существующих компонентов VegaUI в продуктовом коде.
Slot или прямой рендер элемента
- Рендерите элемент напрямую, когда слияние пропсов не нужно.
- Используйте
Slot, когда пропсы родителя нужно слить с выбранным дочерним элементом.
Выбирайте этот компонент, когда
- вы расширяете внутренности дизайн-системы
- вам нужна семантика слияния пропсов с единственным дочерним элементом
Не выбирайте этот компонент, когда
- компонент уже предоставляет
asChild - вам нужен просто обычный элемент
Решение проблем
Композиция падает во время выполнения
Симптом
Рендер завершается ошибкой Slot о форме дочернего элемента.
Вероятная причина
Дочерний элемент не является ровно одним валидным React-элементом.
Как проверить
- посмотрите, что на самом деле передаётся в
children
Решение
Передавайте один валидный React-элемент, а не обычный текст, массивы или фрагменты с несколькими узлами.
Интерфейс выглядит правильно, но семантика неверна
Симптом
Элемент в слоте выглядит правильно стилизованным, но поведение или доступность нарушены.
Вероятная причина
У выбранного дочернего элемента неподходящая для задачи семантика.
Как проверить
- посмотрите итоговый отрендеренный тег
- сравните его с задуманным семантическим элементом
Решение
Выберите правильный дочерний элемент и помните, что Slot меняет только рендер, но не семантику.
Поведение событий кажется неожиданным
Симптом
Обработчики родителя и дочернего элемента словно конфликтуют.
Вероятная причина
Слияние пропсов создало пересекающееся поведение.
Как проверить
- изучите слитые пропсы на итоговом элементе
- протестируйте обработчики родителя и дочернего элемента по отдельности
Решение
Упростите композицию и явно определите, кто отвечает за логику взаимодействия.