Проверка формы в React

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

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

Один, в частности, был опубликован буквально несколько дней назад Хриши Миттал. Его руководство хорошо написано и им легко следовать, но в конечном итоге мне не нравится, что его метод требует, чтобы я изменил способ сохранения состояния формы. И, как он признает, это простой пример, который ограничен одной проверкой на поле и не очень масштабируем.

Итак, я написал свой собственный код проверки реакции и делюсь им с вами.

План

Мои цели в дизайне заключаются в следующем:

  • Переносимость / возможность повторного использования. Я хочу иметь возможность использовать один и тот же код проверки в нескольких местах моего проекта и в будущих проектах.
  • Гибкость. Он должен уметь обрабатывать наиболее распространенные сценарии проверки, такие как проверка, является ли поле пустым, числовым и т. Д.
  • Несколько проверок для каждого поля. Это очень распространенное требование.
  • Проверки, основанные на более чем одном поле. Это более сложный, но удивительно знакомый: совпадает ли первое поле подтверждения пароля с полем пароля? Пользователь заполнил поле телефона или электронной почты?
  • Легко интегрируется. Пожалуйста, не заставляйте меня переделывать мою форму.

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

class FormValidator {
  constructor(validations) {
    // validations is an array of form-specific validation rules
    this.validations = validations;
  }
  validate(state) {
    // iterate through the validation rules and construct
    // a validation object, and return it
    return validation;
  }
}

Чтобы использовать этот объект, моя форма просто создает экземпляр FormValidator, передавая его в массив правил проверки. (Мы обсудим правила проверки в следующем разделе)

const validator = new FormValidator([rule1, rule2, rule3]);

Затем, когда форма хочет проверить, допустимо ли состояние, она вызывает проверку (состояние).

const validation = validator.validate(this.state);

Затем форма может использовать объект проверки, чтобы определить, можно ли отправить форму и что отображать. Мы перейдем к деталям объекта проверки через минуту или две. Но сначала как написать правила?

Правила валидации

Концептуально правило проверки требует следующих вещей:

  1. Какое поле проверяется.
  2. Какую функцию следует вызвать, чтобы проверить, действительна ли она
  3. Что эта функция должна вернуть, если поле допустимо (обычно истина или ложь)
  4. Какое сообщение должно отображаться, если поле недействительно.

К счастью, есть действительно полезный пакет validator, который содержит почти все, что нам нужно для пункта №2. Используя валидатор, мы можем написать

import validator from 'validator'
validator.isEmpty('') // returns true
validator.isEmpty('[email protected]') // returns false
validator.isEmail('[email protected]') // returns true
validator.isEmail('go away') // return false

Хорошо, тогда почему бы не написать правила для поля email вроде этого:

validator = new FormValidator([
  {
    field: 'email',
    method: validator.isEmpty,
    validWhen: false,
    message: 'Please provide an email address.'
  },
  { 
    field: 'email',
    method: validator.isEmail,
    validWhen: true,
    message: 'That is not a valid email.'
  }
]);

Это прекрасно работает! Но подождите, некоторые функции в пакете валидатора принимают параметры или требуют дополнительных аргументов. Предположим, у меня есть поле age, и я хочу использовать функцию validator.isInt, чтобы проверить, находится ли значение от 21 до 65? Итак, давайте добавим в правила аргументы:

validator = new FormValidator([
  ...
  {
    field: 'age',
    method: validator.isInt,
    args: [{min: 21, max: 65}],  // an array of additional arguments
    validWhen: true,
    message: 'Your age must be an integer between 21 and 65'
  }
]

Это все, что нам нужно! С этими правилами и текущим значением состояния формы у экземпляра FormValidator будет все необходимое для проверки правильности формы.

Объект проверки

Прежде чем мы подробно рассмотрим, как работает метод проверки FormValidator, нам нужно подумать о том, что форма должна вернуть. Как мы планируем использовать полученную информацию?

Что ж, наиболее очевидным является то, что мы захотим иметь возможность быстро проверить, действительна ли форма, чтобы мы знали, следует ли нам ее отправлять или нет. Итак, давайте начнем с требования, чтобы у нашего объекта проверки было свойство isValid.

// retrieve the validation object
validation = validator.validate(this.state);
if (validation.isValid) {  // if it's valid
  // handle form submission here
}

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

//add an error class to the div for styling if invalid
<div className={validation.email.isInvalid && 'has-error'}>
  ... // <input> specifics here
  // add the error message below the input field
  <span className="help-block">{validation.email.message}</span>
</div>

Это означает, что наш объект проверки должен иметь объект для каждого поля формы, каждое из которых имеет свойство isInvalid и свойство message. В нашей воображаемой форме с полями электронной почты и возраста это может быть

// let's suppose that state is
state =  { email: 'loony tunes', age: 19}
validation = validator.validate(state)
// the validation object that would result
{
  isValid: false,
  email: {
           isInvalid: true,
           message: 'Please enter a valid email address.'
         },
  age:   {
           isInvalid: true,
           message: 'Your age must be an integer between 21 and 65'
         }
}

Класс FormValidator

Теперь, когда мы определили наши правила проверки и результирующий объект проверки, который нам нужен, мы, наконец, можем создать наш класс FormValidator.

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

import validator from 'validator';
class FormValidator {
  constructor(validations) {
    // validations is an array of rules specific to a form
    this.validations = validations;
  }
  validate(state) {
    // start out assuming valid
    let validation = this.valid();
    // for each validation rule
    this.validations.forEach(rule => {
    
      // if the field isn't already marked invalid by an earlier rule
      if (!validation[rule.field].isInvalid) {
        // determine the field value, the method to invoke and
        // optional args from the rule definition
        const field_value = state[rule.field].toString();
        const args = rule.args || [];
        const validation_method = typeof rule.method === 'string' ?
                                validator[rule.method] :
                                rule.method
        // call the validation_method with the current field value
        // as the first argument, any additional arguments, and the
        // whole state as a final argument.  If the result doesn't
        // match the rule.validWhen property, then modify the
        // validation object for the field and set the isValid
        // field to false
        if(validation_method(field_value, ...args, state) != rule.validWhen) {
          validation[rule.field] = { 
            isInvalid: true, 
            message: rule.message 
          }
          validation.isValid = false;
        }
      }
    });
    return validation;
  }
  // create a validation object for a valid form
  valid() {
    const validation = {}
    
    this.validations.map(rule => (
        validation[rule.field] = { isInvalid: false, message: '' }
    ));
    return { isValid: true, ...validation };
  }
}
export default FormValidator;

Я бросил туда кривую, на которую нужно указать. Мне не нравилось, что я должен помнить import validator from 'validator' в каждой из моих форм, чтобы я мог создавать правила, использующие ее функции. Поэтому я добавил функцию, заключающуюся в том, что если свойство rule.method является строкой, функция validate () использует функцию проверки, названную rule.method. В противном случае он использует переданную вами функцию.

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

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

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

Я надеюсь, что этот пост окажется для вас полезным, и приветствую ваши комментарии и предложения по улучшению. А если вы ищете разработчика программного обеспечения, напишите мне!