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

в 16:40, , рубрики: javascript, React, ReactJS, разработка

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

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

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

У нас есть компонент переключатель — Switch, который принимает проп, давайте пока назовем его something (что-то).

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

switch

<Switch something={fn} />

React дает нам возможность называть проп как нам угодно: handler / clickHandler / onClick / onToggle и т.д

Популярным стало соглашение о том, что название обработчика событий должно начинаться с on, например, onClick. Это связано с тем, что в спецификации HTML есть множество обработчиков, которые уже следуют этому соглашению: onkeydown, onchange, onclick и т.д.

Повторное использование уже существующего соглашения — отличная идея, разработчикам не придется запоминать что-то новое.

Окей, как насчет onClick?

<Switch onClick={fn} />

Здесь я не сторонник имени onClick, такое название предполагает что щелчок мышки это единственный способ взаимодействия с этим компонентом.

Пользователи на мобильном устройстве могут нажимать переключатель пальцем или перетаскивать его вправо. Пользователи с нарушениями зрения могут использовать его с программным обеспечением для чтения с экрана и использовать клавиши клавиатуры.

Как разработчик, использующий этот компонент, я не хочу думать о том как конечные пользователи взаимодействуют с этим компонентом. Я просто хочу прикрепить функцию, которая вызывается при изменении значения.

Давайте такие названия для вашего API, которые не будут указывать на способ взаимодействия:

<Switch onToggle={fn} />

В этом есть смысл, верно? Переключатель toggles (переключается) между двумя значениями.

Внутри компонента вы можете захотеть проксировать все возможные взаимодействия в одной и той же функции

function Switch(props) {
  return (
    <div
      className="switch"
      /* клик для пользователей с мышкой */
      onClick={props.onToggle}
      onKeyDown={function(event) {
        /* если кнопка enter была нажата, вызвать event обработчик */
        if (event.key === 'Enter') props.onToggle(event)
      }}
      onDrag={function(event) {
        /* псевдо код */
        if (event.toElement === rightSide) props.onToggle(event)
      }}
    />
  )
}

Мы учли все варианты чтобы дать понятный API нашим пользователям (разработчикам).

Теперь давайте поговорим о компоненте для ввода текста.

input

<TextInput />

HTML имеет атрибут onchange, документация React использует onChange в своих примерах. Видимо, на счет этого есть консенсус.

<TextInput onChange={fn} />

Очень просто.

Теперь давайте разместим оба эти компонента рядом.

together

<TextInput onChange={fn} />
<Switch    onToggle={fn} />

Видите что-то странное?

Несмотря на то что оба компонента требуют одинакового поведения, проп называется по-разному. Эти пропсы идеально подходят для соответствующих им компонентов, но когда вы смотрите на ваши компоненты вместе, это выглядит довольно противоречиво.

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

Итак, вот совет № 2: стремитесь к последовательным пропсам между компонентами. Одинаковое поведение должно иметь одинаковый проп для всех компонентов.

Этот совет также можно сформулировать так: Стремитесь к минимальной площади API. Вы должны ограничить количество API, которое должен освоить разработчик, прежде чем он начнет продуктивно работать.

Хочу сказать, что все заслуги по этой теме достаются Sebastian Markbåge. (Его выступление: Minimal API Surface Area)

Способ реализации этого совета — выбрать один проп и использовать его во всех ваших компонентах. Из двух пропсов, которые мы имеем в нашем примере, onChange также находится в спецификации HTML, поэтому некоторые разработчики, возможно, узнают его.

together

<TextInput onChange={fn} />
<Switch    onChange={fn} />
<Select    onChange={fn} />
// etc.

Согласованность между компонентами и, как следствие, простота изучения вашего API, перевешивает выбор "идеального" пропа у отдельно взятого компонента.


Небольшой бонус.

Давайте поговорим о подписи у этой функции.

<TextInput onChange={fn} />

Обработчик события onChange (fn в этом примере) получает один аргумент — event.

Он срабатывает при каждом изменении. Вы можете получить кучу полезной информации из этого события

function fn(event) {
  console.log(event.target) // элемент ввода
  console.log(event.target.value) // текст внутри элемента ввода
  console.log(event.which) // какие кнопки клавиатуры были нажаты
}

Скорее всего большинство разработчиков будет заинтересовано в event.target.value, чтобы они могли использовать его для каких-то других задач — использовании в состоянии, отправки формы и т.д.

В случае нашего компонента Switch каждое действие представляет отдельное "событие" — event. Этот event будет иметь разные свойства для щелчка мышкой и перетаскивания. Как мы убедимся, что API согласован?

Мы можем вручную установить event.target.value для каждого "события":

function Switch(props) {
  /* обработчик */
  const fireHandler = event => {
    const newValue = !oldValue

    /* непротиворечивое свойство, на которое могут положиться разработчики: */
    event.target.value = newValue

    /* вызвать обработчик из пропсов */
    props.onChange(event)
  }

  return (
    <div
      className="switch"
      /* клик для пользователей с мышкой */
      onClick={fireHandler}
      onKeyDown={function(event) {
        if (event.key === 'Enter') fireHandler(event)
      }}
      onDrag={function(event) {
        if (event.toElement === rightSide) fireHandler(event)
      }}
    />
  )
}

Автор: IvanGanev

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js