Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Проектирование API компонентов
component_design

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

Композиция, slots, controlled/uncontrolled API, discriminated unions, polymorphism и устойчивые публичные контракты.

Открыть лабораториюv0.1.0Запускается локально из публичного репозитория

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

Хороший компонент делает правильное использование простым, а незаконное — непредставимым или хотя бы немедленно диагностируемым.

#Результат урока

Вы спроектируете публичный API без boolean-взрыва, скрытой связи с приложением и утечки DOM-деталей.

До начала достаточно props, children, state ownership и строгие TypeScript unions. В конце должен появиться не конспект, а доступный набор Alert, Dialog и Select primitives с типовыми контрактами и управляемыми вариантами.

#1. Стабильная ответственность компонента

Компонент получает один связный набор обязанностей. Visual primitive знает семантику, layout и состояния взаимодействия; feature знает данные, права и команды. Импорт API-клиента в Button или Dialog связывает библиотеку с приложением. Публичный контракт оценивают по изменчивости: что должно меняться вместе, а что независимо.

Рабочая проверка. Отделите подтверждающий Dialog от команды удаления записи.

Типичная поломка. Сделать DeleteUserDialog, который сам читает route, auth и cache в общем UI package.

#2. Union вместо boolean-взрыва

Флаги primary, danger, compact, loading, success быстро создают десятки сочетаний, часть которых бессмысленна. Взаимоисключающие режимы выражают variant, а состояния с разными обязательными props — discriminated union. Независимые аспекты можно оставить отдельными.

Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.

type NoticeProps = | { kind: 'info'; message: string } | { kind: 'error'; message: string; retry?: () => void } | { kind: 'success'; message: string; receiptId: string };

retry и receiptId доступны только там, где имеют смысл; потребитель не может передать receipt для info.

Рабочая проверка. Преобразуйте три взаимно исключающих boolean props в один union.

Типичная поломка. Разрешить одновременно error и success и выбирать победителя порядком if.

#3. Controlled и uncontrolled API одного primitive

Reusable primitive может поддерживать controlled open + onOpenChange и uncontrolled defaultOpen. Внутренний helper выбирает владельца один раз; переключение режима после mount не поддерживается. Callback сообщает следующий state и причину, если потребителю важно отличать Escape, trigger и outside interaction.

Рабочая проверка. Проверьте controlled Dialog, где родитель отклоняет закрытие при несохранённом draft.

Типичная поломка. Смешать open и локальный state так, что UI расходится с prop.

#4. Композиционные slots и compound components

Named slots подходят, когда структура фиксирована. Compound components дают гибкую вложенную разметку с общим context, например Tabs.List/Trigger/Content. Context должен хранить только координацию primitive, а не весь feature state. Compound API документирует допустимую структуру и проверяет отсутствие provider понятной ошибкой.

Рабочая проверка. Соберите Tabs из List, Trigger и Content с одним внутренним контекстом selection.

Типичная поломка. Использовать cloneElement и неявно модифицировать произвольных children.

#5. DOM props и безопасный spread

ComponentPropsWithoutRef<"button"> помогает наследовать нативные props, но spread order важен. Защищённые role/type/aria нельзя случайно перезаписать. Для Button разумно задать type="button" по умолчанию, иначе внутри формы он submit. Event callbacks можно скомпоновать, не теряя consumer handler.

Следующий фрагмент показывает минимальный рабочий контракт, а не заготовку для слепого копирования.

type ButtonProps = React.ComponentPropsWithoutRef<'button'> & { tone?: 'normal' | 'danger' }; function Button({ tone = 'normal', type = 'button', ...props }: ButtonProps) { return <button {...props} type={type} data-tone={tone} />; }

Явный type после spread защищает выбранный контракт; если consumer должен его менять, значение уже извлечено и передано осознанно.

Рабочая проверка. Поместите Button в форму и докажите, что обычное действие не отправляет её.

Типичная поломка. Безусловно spread-ить props после внутренних aria/event props.

#6. Polymorphic as с ограничениями

Polymorphic component позволяет выбрать element type, но усложняет вывод props, refs и семантику. Button, отрендеренный как div, не получает клавиатурное поведение автоматически. Часто безопаснее отдельные Button и Link primitives или slot pattern с проверенной семантикой. API as оправдан в низкоуровневой библиотеке с тестами типов.

Рабочая проверка. Сравните типы и keyboard behavior Button, LinkButton и чрезмерно общего as API.

Типичная поломка. Разрешить as="div" и считать data-role эквивалентом button.

#7. Версионирование публичного API

Shared component library — зависимость. Переименование prop, смена default, DOM-структуры или timing callback может быть breaking change. Контракт фиксируют type tests, interaction tests, accessibility tests и visual review. Deprecated adapter даёт миграционный путь, но не должен жить бесконечно.

Рабочая проверка. Добавьте новый variant без изменения defaults существующих потребителей и напишите migration note.

Типичная поломка. Считать изменение DOM wrappers внутренним, хотя consumer CSS и aria relationships от него зависят.

#8. API review через примеры потребителя

До реализации напишите несколько usage examples: простой случай, controlled, async, error и доступность. Если обычный сценарий требует boilerplate, а опасный — короткий, API перевёрнут. Проверяйте illegal states, ownership и escape hatches. Не добавляйте prop ради единственного потребителя, пока композиция решает задачу.

Рабочая проверка. Проведите API review Dialog на пяти сценариях и удалите props, заменяемые children/actions.

Типичная поломка. Проектировать API только из внутренней реализации компонента.

#Лаборатория

Переработайте перегруженный Modal с 14 props в composable Dialog API. Отделите trigger/content/actions, controlled open state, destructive confirmation и асинхронное закрытие. Сохраните семантику и focus contract.

Критерий завершения: невалидные сочетания props отклоняются TypeScript, бизнес-данные остаются у feature, а базовый компонент проверяется независимо. Сначала зафиксируйте наблюдаемое поведение тестом или измерением, затем меняйте реализацию.

#Источники для сверки

  • React: Passing Props
  • React: Sharing State
  • TypeScript discriminated unions

Устранение неисправностей

Проверьте свои знания

Вопросы ещё не добавлены

Вопросы для этой подтемы ещё не добавлены.

Далее: Reducers, Context и внешние хранилища