Проектирование API компонента
Что это такое
API компонента — это набор входных данных, которые он принимает, и обещаний, которые даёт взамен: свойства и слоты, их типы и значения по умолчанию, события, которые он испускает, и точки подключения стилей, которые открывает. Проектировать такой API — та же работа, что проектировать сигнатуру функции, только вызывающий здесь другой: разработчик, который пишет разметку, а ошибку обнаруживают через месяцы, да и то на другом экране.
Почему это важно
Компонент используется сотни раз, поэтому каждая неоднозначность в его API умножается на сотни. Свойство, которое означает две вещи, превращается в два поведения, которые надо поддерживать; булево значение, у которого неверное значение по умолчанию, — в тысячу переопределений в одну строку; а точка подключения стилей, которая выдаёт внутреннее имя класса, становится непоправимой, как только от неё зависят три команды. Правильный API дёшев при первом использовании и дорог при пятидесятом — поэтому он заслуживает тех двадцати минут размышления, которые часто достаются телу компонента.
Ключевые идеи
Предпочитайте небольшой закрытый набор вариантов
Свойство variant с тремя именованными значениями документирует себя и поддаётся проверке. Три булевых значения, которые дают восемь состояний, из которых три бессмысленны, — нет.
<Button variant="primary" />
<Button variant="secondary" />
<Button variant="danger" />
Вопрос, который стоит задать каждому булеву значению: «может ли оно когда-нибудь осмысленно сочетаться с другим?» Если нет — перед вами одно перечислимое значение.
Делайте частый случай значением по умолчанию
Самое частое использование не должно требовать свойств. Если большинство мест вызова передают size="medium", сделайте medium значением по умолчанию и уберите свойство из этих мест: так и читать короче, и менять потом легче.
Слоты — для структуры, свойства — для данных
Передавайте содержимое как дочерние элементы или именованные слоты, а не как свойства, набитые разметкой. Компонент, который принимает слот footer, можно переиспользовать так, как его автор не представлял; компонент, который принимает footerHtml строкой, безопасно расширить нельзя вообще.
<Card>
<Card.Header>Счёт №1042</Card.Header>
<Card.Body>{lines}</Card.Body>
</Card>
Не выдавайте внутреннее устройство
Открывайте смысловые точки подключения, а не внутренние имена классов. Если DOM компонента изменится, потребителям не придётся ничего менять. Там, где доступны переменные CSS, их и стоит открывать: это намеренно маленькая и стабильная поверхность.
.badge {
--badge-bg: navy; /* публичное */
--badge-pad: 0.25rem;
background: var(--badge-bg);
padding: var(--badge-pad);
}
Решите, кому принадлежит состояние
Компонент, который держит своё состояние, легко вставить, но трудно контролировать. Компонент, который принимает значение и onChange, многословнее, но им может управлять приложение. Выберите один вариант на компонент и укажите его в документации: компонент, который иногда управляемый, а иногда нет, — худшее из обоих.
Практика
Перепишите компонент с флагами на варианты
Найдите компонент с двумя или более булевыми свойствами, которые никогда не бывают истинны одновременно. Замените их одним перечислимым свойством и обновите каждое место вызова.
Готово, когда: недопустимые сочетания стали невыразимыми, а в исходнике компонента стало на одну ветку меньше.
Задокументируйте компонент, используя его
Напишите документацию для одного существующего компонента в виде трёх примеров использования: самый короткий возможный вызов, самый частый вызов и самый сложный вызов, который действительно нужен вашему продукту. Реализуйте недостающий слот, если третий пример его вскрывает.
Готово, когда: все три примера работают, самый короткий вызов не требует свойств, и ни один пример не лезет во внутреннее имя класса.
Частые ошибки
- Булево свойство на каждую вариацию — сочетания множатся, и недостижимые состояния становятся возможными; исправить: замените взаимоисключающие булевы значения одним перечислимым вариантом.
- Внутренние имена классов как точки подключения — переименование класса ломает совместимость во всём продукте; исправить: открывайте пользовательские свойства или документированные имена слотов.
- Обязательные свойства для частого случая — каждое место вызова повторяет одно и то же значение, и изменение означает правку всех; исправить: выберите самое частое значение значением по умолчанию.
- Разметка как строковое свойство — вызывающий не может вложить внутрь компоненты, а экранирование становится опасным; исправить: принимайте дочерние элементы или элемент слота.
- Нечёткость с управляемым состоянием — компонент иногда игнорирует переданное значение, и это читается как ошибка; исправить: выберите для каждого компонента внутреннее или управляемое состояние и задокументируйте его.
Что читать дальше
- Язык гипертекстовой разметки (HTML) (02-frontend/03-html) — семантика элементов, которые вы оборачиваете, — часть API, а не деталь реализации.
- Композиция и темизация компонентов (02-frontend/12-ui-components/02-composing-and-theming-components) — следующий шаг: заставить несколько компонентов работать как один.
- Дизайн-системы (03-design-ux/32-design-systems) — более широкая практика, частью которой является API компонента.