Пишем API для React компонентов, часть 1: не создавайте конфликтующие пропсы

Пишем API для React компонентов, часть 2: давайте названия поведению, а не способам взаимодействия

Пишем API для React компонентов, часть 3: порядок пропсов важен

Пишем API для React компонентов, часть 4: опасайтесь Апропакалипсиса!

Пишем API для React компонентов, часть 5: просто используйте композицию

Пишем API для React компонентов, часть 6: создаем связь между компонентами

Поговорим о компоненте Avatar.


avatar-1


<Avatar image="simons-cat.png" />

Аватары встречаются повсюду в приложениях и обычно бывают разных размеров. Вам нужен большой аватар для профиля пользователя, маленький в выпадающих списках и несколько промежуточных.


github-avatars


Давайте добавим проп size (проп для размеров).


Мы не хотим давать разработчику возможность задавать произвольную ширину и высоту, вместо этого мы хотим дать разработчику несколько возможных размеров.


avatar-2


<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-3


<Avatar type="user" image="simons-cat.png" />
<Avatar type="app"  image="firebase.png" />

Выглядит неплохо, разве нет?


И да, мы получаем поддержку нескольких размеров для аватара приложения, потому что это один и тот же компонент. Вообще, нам это не нужно, но почему бы и нет, раз уж это ничего не стоило для нас


avatar-4


<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-6


<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-7


<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 для двух разных компонентов аватара:


avatar-7


<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 есть несколько вещей, на которые стоит обратить внимание:


  1. Мы смогли удалить ненужные функции, такие как size (размер), в AppAvatar.
  2. Мы сократили размер нашего API, сохранив одинаковое имя fallback (запасной вариант) для двух компонентов.
    Помните совет № 2 этой серии? Мы хотим, чтобы ��азработчики думали о поведении (fallback), а не о взаимодействии/реализации (инициалы или иконки). Это помогает разработчикам быстрее изучать API и способы использования компонентов.
  3. Мы также можем избежать любых конфликтующих пропсов.
  4. Наконец, мы сократили количество пропсов. Посмотрите на таблицу пропсов, она стала намного чище:

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

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




Для дальнейшего изучения:


  1. лекция Jenn Creighton, где она рассказывает про Апропакалипс
  2. лекция Sandi Metz про дубликаты и абстракции