Исходя из опыта работы с Vue.js, переход на React был для меня довольно неприятным. Одной из многих вещей, которые мне было намного сложнее реализовать (и под реализацией я подразумеваю сделать это правильно), была проверка формы и привязка данных. Если вы знакомы с Vue, вы знаете, что у него есть волшебная штука под названием v-model. Вы также можете создавать собственные привязки v-модели и создавать компоненты с самопроверкой. Если вы не знакомы с Vue, но все еще кодируете React, что угодно. Я обещаю, что это не статья о Vue.js. Я попробую.

Для меня после работы часы часто были похожи - гуглил «Как проверять формы React», «Как использовать хуки для проверки форм» и т. Д. Я действительно хотел найти или изобрести способ, который сэкономит время и даст хорошие результаты. Я был мотивирован созданием пользовательских интерфейсов с множеством форм на моем рабочем месте, поэтому я хотел тратить меньше времени на реализацию привязки данных и проверку формы, потому что это потребовало много времени и энергии.

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

Крючки.

Это слово, да. Крючки.

Меня вдохновил хук ввода Дэна Абрамова, который, должно быть, выглядел примерно так (извинения, пишу из головы):

export function useInput(initialValue = '') {
  const [value, setValue] = useState(initialValue);
  const onChange = e => setValue(e.target.value);
  return { onChange, value }
}

Это начало. Это ловушка, которую вы будете вызывать для ввода, например,

<input type="text" {...useInput('initValue')} />

И он будет управлять своим значением - то, что мы получаем от ловушки, - это функция onChange и входное значение, поэтому допустим, что он обновится сам. Вы можете добавить туда некоторую обработку ошибок (привязать класс ошибки - например, is-invalid, если вы используете загрузочный CSS), но это почти все. У вас по-прежнему нет двусторонней связанной формы, у вас просто есть своего рода самоконтролируемый ввод. Но это только начало.

Итак, что я создал?

Я создал файл с перехватчиками и проверочными функциями, который может быть не очень приятным для просмотра, но он хорошо подходит для моих нужд. Я все еще занимаюсь рефакторингом. Плюс в том, что мне пришлось закодировать его только один раз, и я буду использовать его для проверки своих форм, пока не найду / не создам что-то получше. Он используется в компоненте следующим образом:

Это компонент с двумя полями (Material UI) и одной кнопкой. Кнопка неактивна, если поля недействительны. Это, на мой взгляд, довольно короткий и скудный код. Если мы говорим о привязке и проверке данных формы React, это мечта разработчика.

Что там на самом деле происходит?

Мы начинаем с вызова ловушки useForm и передачи в него значений по умолчанию. useForm - это то место, где происходит большая часть упомянутого волшебства. Он также кое-что возвращает - values ​​, useInput hook и isValid логическое свойство. (Он возвращает больше, но пока не буду вас путать, поскольку мы будем использовать только эти 3.)

const { values, useInput, isValid } = useForm({
  username: '',
  email: ''
});
  • values ​​- объект значений изменяется в реальном времени. Это означает, что вам не нужны обработчики onChange и setState, все это происходит внутри наших хуков, которые просто позаботятся о том, чтобы вы получили единственное, что вас интересует - ваш ценности вместе.
  • useInput - ловушка, которую вы используете во входных данных, как тот, который я показал ранее. Первый аргумент - это имя входа, которое также является свойством, к которому будет осуществляться доступ в объекте values ​​. Второе свойство - это проверка, которую вы можете передать либо как строку, либо как объект.
<TextField
  label="Username *"
  {...useInput('username', 'isRequired')}
/>
  • isValid - логический результат проверки. Я использую его, чтобы отключить кнопку отправки, чтобы пользователь мог видеть, что он не может отправить форму.
<Button type="submit" disabled={!isValid}>
  Submit
</Button>

Это действительно все, что нужно для создания простых форм.

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

useForm (defaultValues, invalidAttr)

Итак, я сказал вам, что мы передаем в ловушку useForm значения по умолчанию. Мы также можем передать второй атрибут - объект - который будет нашими свойствами ошибки, которые будут добавлены к входным данным, если они недействительны. При использовании компонентов пользовательского интерфейса материала мы заинтересованы в добавлении error = ”true” к нашим компонентам, как это

<TextField error/>

Но что, если мы используем Bootstrap, Bulma, собственный CSS или другой фреймворк пользовательского интерфейса? Что ж, мы можем передать наши варианты простым способом:

const { values } = useForm(
  { name: '' },
  { className: 'is-invalid' }
)

Вы можете выбрать любой нужный недопустимый атрибут или объединить несколько значений.

Это весь мой код useForm. Как видите, существуют переменные values ​​ и setValues ​​ (для хранения и изменения значений), formErrors и setFormErrors (для хранения и изменения ошибок) и смонтированный логический флаг.

Большая часть того, что делает этот настраиваемый хук useForm, - это

  • он контролирует смонтированное состояние (я добавил это, потому что кнопка была изначально действительна в течение миллисекунды, и я хотел, чтобы она сразу не мигала как недействительная - это отвлекало)
  • он создает другой (useInput) хук и передает его свойства в (например, formHandler или errorHandler, чтобы дочерний ввод мог переписать значения или наши свойства ввода ошибок)
  • он возвращает values ​​, функцию для ручного setValues ​​ (если вам нужно сбросить форму после отправки), useInput, errors (объект с поля и их первые неудовлетворенные правила) и логическая переменная isValid, для которой требуется, чтобы форма была смонтирована, а ошибки должны быть пустыми, чтобы быть истинным .

useInput (имя, проверка)

Итак, useForm дает нам доступ к этой ловушке useInput, которая контролирует наши значения, ошибки и принимает имя ввода и критерии проверки.

Здесь мы занимаемся многими вещами. Мы хотим знать, был ли ввод сфокусирован, изменен, размыт и т. Д. - для управления значением, а также отображением ошибок. Мы не хотим, чтобы поле ввода светилось красным, например, если вы еще не прикасались к нему. Часть кода просто устанавливает такие значения, как handleBlur или handleFocus, я не буду вдаваться в подробности.

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

useEffect(() => {
  handleValidation();
}, [handleValidation, name]);

Мы также проверяем его каждый раз, когда меняем значение. handleValidation() воссоздается при каждом изменении значения, поэтому массив useEffect dependencies заботится о том, чтобы наша функция работала, когда мы этого хотим.

И нам также нужно следить за внешними изменениями нашего собственного ввода, если вы используете setValues ​​ из useForm где-то в вашем компоненте.

useEffect(() => {
  if (value !== formValue) {
    setValue(formValue);
    setIsTouched(false);
    setIsFocused(false);
  }
}, [formValue, value, setValue, setIsFocused, setIsTouched]);

Это вызывает изменение значения и вызывает проверку.

Также изменяется родительский значения - handleChange ожидает получить событие (или любой объект, имеющий свойство target: {value}) и вызывает родительскую функцию setFormData для обновления весь объект.

const handleChange = useCallback(({ target }) => {
  const { value, checked, type } = target;
  const newValue = type === 'checkbox' ? checked : value;
  // using dot helps us change nested values
  let data;
  const isNested = name.includes('.');
  if (isNested) {
    dot.override = true;
    data = dot.str(name, newValue, { ...formData });
  }
  else data = { ...formData, [name]: newValue };
  setValue(newValue);
  setFormData(data);
}, [setValue, formData, setFormData, name]);

Обновление: проверка также может работать со значениями вложенных имен с использованием библиотеки dot. Если вам интересно больше, вы можете увидеть демонстрационное приложение в конце статьи. Он используется так:

<input {...useInput('member.username', 'isRequired')}>

Сама проверка передается на аутсорсинг моей функции validate, которая либо возвращает первое неудовлетворенное правило, либо значение null. Если у нас есть невыполненное правило, мы добавляем name ввода и правило в наш объект ошибки, который находится в ловушке useForm.

const handleValidation = useCallback(() => {
  const isValid = validate(value, validationRules);
  setIsValid(isValid);
  handleError(name, isValid);
}, [setIsValid, validationRules, name, value, handleError]);

проверить (значение, проверка)

export function validate(value, validation) {
  const fieldsToValidate = {};
  let trimmedValidation;

  switch (typeof validation) {
    case 'object':
      Object.keys(validation).forEach(property => {
        fieldsToValidate[property] = validation[property]
      });
      break;

    case 'string':
    default:
      if (!validation.length) return null;

      trimmedValidation = validation.replace(/ /g, '');
      trimmedValidation.split(',').forEach(fieldName => {
        fieldsToValidate[fieldName.trim()] = true;
      });
  }

  // check whether we do need to validate at all
  const isRequired = fieldsToValidate.isRequired || fieldsToValidate.isEmpty === false;
  if (!value && !isRequired) return null;

  let unmetValidationRule = null;
  let isValid = true;

  Object.keys(fieldsToValidate).forEach(rule => {
    // don't proceed if we're already invalid
    if (!isValid) return;

    const options = fieldsToValidate[rule];

    switch (rule) {
      case 'isRequired':
        if (!value) isValid = false;
        break;

      default:
        switch (options) {
          case true:
          case null:
            isValid = validator[rule](value);
            break;
          case false:
            isValid = !validator[rule](value);
            break;
          default:
            isValid = validator[rule](value, options);
        }
    }

    if (!isValid) unmetValidationRule = rule;
  });

  return unmetValidationRule || null;
}

Итак, в основном, если я получаю строку, я анализирую ее на объект, и если я получаю объект, я просто перемещаю значения. Я использую переменную fieldsToValidate, которая после заполнения выглядит так

{
  isRequired: true,
  isLength: { min: 6 }
}

учитывая, что он принадлежит входу, который выглядит следующим образом (вы можете передать объект проверки, если вам нужны более сложные правила)

<input {...useInput('username', {
  isRequired: true,
  isLength: { min: 6 }
})}>

Как видите, я создал собственное правило isRequired. Мне было удобно использовать, если я хочу пройти проверку строки. Я использую пакет npm validator, в котором есть множество функций на ваш выбор. Я также не проверяю поля, если в них нет значения, а в правилах сказано, что оно не требуется (с помощью ‘isRequired’: true или ‘isEmpty’: false) валидатора. Затем я возвращаю невыполненное правило или null. Потомок добавляет или удаляет свою ошибку из объекта ошибки и .. вот и все!

Другой пример формы

У этого есть настраиваемые атрибуты ошибок, используется метод валидатора isEmail, а также isInt. Как видите, вы можете передавать методы, которые принимает валидатор. Он может не работать со всеми их методами из-за переданных опций / параметров. Этот код можно улучшить, и вы определенно сможете это сделать, если захотите. А пока мне нужно было что-то маленькое и простое.

Код с открытым исходным кодом

Если вы хотите использовать эту проверку формы, она находится в моем репозитории на GitHub - https://github.com/DJanoskova/React-validator-demo. Вам не нужно собирать код из этой статьи.

Есть также другие примеры и живая демонстрация, которые находятся по адресу https://react-form-hook-validator.herokuapp.com/.

Спасибо за чтение, удачного подтверждения!