Пишем API для React компонентов, часть 1: не создавайте конфликтующие пропсы
Пишем API для React компонентов, часть 2: давайте названия поведению, а не способам взаимодействия
Пишем API для React компонентов, часть 3: порядок пропсов важен
Пишем API для React компонентов, часть 4: опасайтесь Апропакалипсиса!
Пишем API для React компонентов, часть 5: просто используйте композицию
Пишем API для React компонентов, часть 6: создаем связь между компонентами
Поговорим о компоненте Avatar
.
<Avatar image="simons-cat.png" />
Аватары встречаются повсюду в приложениях и обычно бывают разных размеров. Вам нужен большой аватар для профиля пользователя, маленький в выпадающих списках и несколько промежуточных.
Давайте добавим проп size
(проп для размеров).
Мы не хотим давать разработчику возможность задавать произвольную ширину и высоту, вместо этого мы хотим дать разработчику несколько возможных размеров.
<Avatar size="xsmall" image="simons-cat.png" />
<Avatar size="small" image="simons-cat.png" />
<Avatar size="medium" image="simons-cat.png" />
<Avatar size="large" image="simons-cat.png" />
<Avatar size="xlarge" image="simons-cat.png" />
В cosmos у нас еще есть аватары для приложений. Мы хотим, чтобы они выглядели немного иначе — округленный квадрат вместо круга.
Часть создания хорошего API заключается в том, чтобы дать разработчикам возможность думать о данных, а не о дизайне — дизайн должен быть уже заключен в компоненте
Мы можем добавить еще один проп, различающий два типа аватара. Один маленький проп ведь не может навредить, да?
<Avatar type="user" image="simons-cat.png" />
<Avatar type="app" image="firebase.png" />
Выглядит неплохо, разве нет?
И да, мы получаем поддержку нескольких размеров для аватара приложения, потому что это один и тот же компонент. Вообще, нам это не нужно, но почему бы и нет, раз уж это ничего не стоило для нас
<Avatar type="app" size="xsmall" image="firebase.png" />
<Avatar type="app" size="small" image="firebase.png" />
<Avatar type="app" size="medium" image="firebase.png" />
<Avatar type="app" size="large" image="firebase.png" />
<Avatar type="app" size="xlarge" image="firebase.png" />
Давайте поговорим о том как на самом деле разработчик будет использовать этот компонент в своем приложении. Пользовательская информация, вероятно, поступает из API и содержит URL-адрес аватара. Разработчик будет передавать эту информацию компоненту Avatar
при помощи пропсов.
И если пользователь еще не загрузил аватар, мы хотим показывать значение по умолчанию, то же самое касается и логотипа приложения.
<Avatar type="user" image={props.user.avatar} />
<Avatar type="user" image={missing} />
<Avatar type="app" image={props.app.logo} />
<Avatar type="app" image={missing} />
Картинка по умолчанию уже включена в компонент, так что мы не просим у разработчика изображения картинки по умолчанию.
Это хороший запасной вариант, но мы можем добиться большего.
Мы можем показать инициалы имени пользователя с уникальным фоном (Gmail сделал этот шаблон популярным, он помогает быстро различать людей)
<Avatar type="user" image={props.user.avatar} />
<Avatar type="user" image={missing} />
<Avatar type="user" initials={props.user.intials} image={missing} />
<Avatar type="app" image={props.app.logo} />
<Avatar type="app" image={missing} />
<Avatar type="app" icon={props.app.type} image={missing} />
Давайте посмотрим на все пропсы, которые поддерживает наш компонент:
имя | описание | тип | значение по умолчанию |
---|---|---|---|
image |
URL картинки | string |
- |
size |
размер аватара | string: один из [xsmall, small, medium, large, xlarge] |
small |
type |
тип аватара | string: один из [user, app] |
user |
initials |
инициалы пользователя как запасной вариант | string |
- |
icon |
иконка для отображения как запасной вариант | string: один из [list of icons] |
- |
Мы начали с простого компонента аватара, но теперь он поддерживает все эти пропсы и поведение!
Когда вы видите компонент, который поддерживает много пропсов, он, вероятно, пытается сделать слишком много вещей. Вы только что создали Апропакалипс (Apropcalypse).
Придумала это понятие Jenn Creighton.
Мы пытаемся заставить наш компонент Avatar
работать для пользователей и для приложений, и при этом работать с различными размерами и различными вариантами запасного поведения.
Это также допускает странные комбинации, которые не рекомендуются применять, такие как аватар приложения с резервным текстом. Помните, это был совет № 1 — не создавайте конфликтующие пропсы (смотрите Пишем API для React компонентов, часть 1: не создавайте конфликтующие пропсы)!
Окей, как мы будем разбираться с этим? Создайте два разных компонента.
Совет:
Не бойтесь создавать новый компонент вместо того что бы добавлять пропсы и дополнительную логику в уже существующий компонент.
Во-первых, вот как будет выглядеть API для двух разных компонентов аватара:
<UserAvatar size="large" image="simons-cat.png" />
<UserAvatar size="large" image="" />
<UserAvatar size="large" fallback="LA" image="" />
<AppAvatar image="firebase.png" />
<AppAvatar image="" />
<AppAvatar fallback="database" image="" />
В этом API есть несколько вещей, на которые стоит обратить внимание:
- Мы смогли удалить ненужные функции, такие как
size
(размер), вAppAvatar
. - Мы сократили размер нашего API, сохранив одинаковое имя
fallback
(запасной вариант) для двух компонентов.
Помните совет № 2 этой серии? Мы хотим, чтобы разработчики думали о поведении (fallback
), а не о взаимодействии/реализации (инициалы или иконки). Это помогает разработчикам быстрее изучать API и способы использования компонентов. - Мы также можем избежать любых конфликтующих пропсов.
- Наконец, мы сократили количество пропсов. Посмотрите на таблицу пропсов, она стала намного чище:
UserAvatar:
имя | описание | тип | значение по умолчанию |
---|---|---|---|
image |
URL картинки | string |
- |
size |
размер аватара | string: один из [xsmall, small, medium, large, xlarge] |
small |
fallback |
инициалы пользователя как запасной вариант | string |
- |
AppAvatar:
имя | описание | тип | значение по умолчанию |
---|---|---|---|
image |
URL картинки | string |
- |
fallback |
иконка как запасной вариант | string: один из [list of icons] |
- |
Единственное, что меня немного огорчает в этом API, это то, что хотя оба компонента имеют одно и то же имя и тип для fallback: string
(запасной вариант с типом string
— строка), один из них принимает две буквы для инициалов, в то время как другой — имя иконки.
Давайте поговорим о реализации. Соблазнительно создать компонент BaseAvatar
, который будут использовать и UserAvatar
, и AppAvatar
, при этом некоторые пропсы будут заблокированы.
function UserAvatar(props) {
return (
<BaseAvatar
image={props.image}
type="user"
initials={props.fallback}
/>
)
}
render(<UserAvatar fallback="LA" />)
И это совсем не плохая идея. Но довольно трудно в самом начале предвидеть что может нам понадобиться. Мы плохо предсказываем будущие требования.
Начните с двух разных компонентов, и по мере их развития вы сможете начать видеть похожие шаблоны и найти хорошую абстракцию. Отсутствие абстракции гораздо лучше чем неправильная абстракция.
Дублирование лучше неправильной абстракции — Sandi Metz
Вернитесь к своему коду и найдите тот компонент, который принимает слишком много пропсов, и посмотрите, можно ли его упростить, разбив его на несколько компонентов.
Для дальнейшего изучения:
- лекция Jenn Creighton, где она рассказывает про Апропакалипс
- лекция Sandi Metz про дубликаты и абстракции