Пишем 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 про дубликаты и абстракции
