- PVSM.RU - https://www.pvsm.ru -
Привет!
Валидация форм — одна из самых важных задач на сайте. Нам приходится валидировать данные на присутствие, на совпадение по паттерну, создавать асинхронные валидации, применять валидации только после снятия фокуса с поля или перед отправкой формы… Порой это становится настоящей головной болью для разработчика.
Vue.js содержит много интересных и необычных подходов к валидации, которые помогут решить ваши проблемы. Обзор под катом!
Используйте Vuelidate [1].
HTML5 дал разработчикам возможность валидации форм с помощью новых атрибутов для полей и Validation API [2]. Мы можем использовать их прямо в наших vue-шаблонах.
Вот, например, простая форма регистрации, состоящая из трех полей: поля для электронной почты, для пароля и повтора пароля. Для первых двух мы используем валидации с помощью атрибутов, для третьего — с помощью атрибутов и Validation API:
<template>
<form @submit.prevent="someAction()">
<input v-model="email" type="email" required>
<input v-model="password" type="password" required>
<input v-model="repeatedPassword" type="password" required ref="repeatedPasswordEl">
<button type="submit">
Отправить форму
</button>
</form>
</template>
<script>
export default {
data() {
return {
email: null,
password: null,
repeatedPassword: null,
};
},
watch: {
repeatedPassword: 'checkPasswordsEquality',
password: 'checkPasswordsEquality',
},
methods: {
checkPasswordsEquality() {
const { password, repeatedPassword } = this;
const { repeatedPasswordEl } = this.$refs;
if (password !== repeatedPassword) {
repeatedPasswordEl.setCustomValidity(
'Пароли должны совпадать',
);
} else {
repeatedPasswordEl.setCustomValidity('');
}
},
},
};
</script>
Даже в таком простом примере можно увидеть много проблем:
Если у вас в проекте всего одна форма, и та используется только вами, то HTML5 валидации — отличный выбор. Во всех остальных остальных случаях старайтесь использовать другие подходы.
Другая частая проблема, которую я встречаю в проектах — валидации без использования библиотек.
<template>
<form @submit.prevent="someAction()">
<input
v-model="email"
type="email"
@blur="isEmailTouched = true"
:class="{ error: isEmailError }"
>
<div v-if="isEmailError">
Поле заполено неверно
</div>
<button :disabled="!isEmailValid" type="submit">
Отправить форму
</button>
</form>
</template>
<script>
const emailCheckRegex = /^...Длинный RegExp для валидации Email...$/;
export default {
data() {
return {
email: null,
isEmailTouched: false,
};
},
computed: {
isEmailValid() {
return emailCheckRegex.test(this.email);
},
isEmailError() {
return !this.isEmailValid && this.isEmailTouched;
},
},
};
</script>
В таком подходе нет проблем. За исключением того, что в формах с количеством полей больше одного рождается много повторяющегося кода, и каждый разработчик пишет валидации по-своему.
Лучший выход из всех проблем — пользоваться библиотеками, которые предлагает нам сообщество. На текущий момент есть два популярных решения для валидации форм:
Каждая имеет свой уникальный подход, которые мы подробно рассмотрим ниже.
vee-validate — библиотека для валидаций, появившаяся во времена Vue.js первой версии, имеет большое сообщество и используется в большом количестве проектов.
Возраст сказывается на весе — 31 КБайт (minified+GZIP), в полтора раза больше самого Vue.js! Связано это с тем, что библиотека содержит в себе сразу кучу вещей:
Библиотека поддерживает 41 язык для ошибок. Чтобы установить и использовать ее с нужной локализацией, требуется выполнить всего пару шагов:
npm i vee-validate
/* main.js */
import Vue from 'vue';
import VeeValidate, { Validator } from 'vee-validate';
import ru from 'vee-validate/dist/locale/ru';
Validator.localize('ru', ru);
Vue.use(VeeValidate, {
locale: 'ru',
});
/* ... */
У vee-validate есть два подхода к валидациям: с помощью директивы и с помощью компонентов.
Подход с помощью директивы очень прост: вы вешаете на поле ввода директиву v-validate, которой передаете список валидаторов. Состояние валидации и строчки ошибок затем можно получить из полей fields и errors внутри компонента.
Чтобы посмотреть ее использование на примере, давайте сделаем простую форму, которая:
— Содержит поле «Серия и номер паспорта»
— Содержит поле «Дата выдачи паспорта»
— Содержит поле «Имя»
— Добавляет атрибут disabled на кнопку отправки формы если данные неверны
<template>
<form @submit.prevent="someAction()">
<div>
<!--
Ошибка в поле будет отображена сразу после начала ввода в него данных
data-vv-as - атрибут для кастомизации названия поля в строчке с ошибкой
name - название поля внутри объекта валидатора
-->
<input
type="text"
v-model="passportData"
v-validate="{ required: true, regex: /^d{4} d{6}$/ }"
data-vv-as="серия и номер паспорта"
name="passport_data"
>
<span v-if="errors.has('passport_data')">
{{ errors.first('passport_data') }}
</span>
</div>
<div>
<!--
По дефолту все ошибки отобразятся как только вы начнете вводить данные.
Для изменения этого поведения используйте v-model-модификатор lazy
или флаги валидации внутри fields.passport_date
Флаг invalid означает то, что данные в поле неправильные
Флаг touched означает, что поле ввода создало событие blur
-->
<input
type="text"
v-model.lazy="passportDate"
v-validate="{ required: true, date_format: 'dd.MM.yyyy' }"
data-vv-as="дата выдачи паспорта"
name="passport_date"
>
<span
v-if="fields.passport_date
&& fields.passport_date.touched
&& fields.passport_date.invalid"
>
{{ errors.first('passport_date') }}
</span>
</div>
<div>
<!--
Если хотите отобразить сразу все ошибки поля,
то используйте модификатор continues и метод errors.collect()
-->
<input
type="text"
v-model="name"
v-validate.continues="{ required: true, alpha: true, max: 10 }"
data-vv-as="имя"
name="name"
>
<span
v-for="error in errors.collect('name')"
:key="error"
>
{{ error }}
</span>
</div>
<button
type="submit"
:disabled="!isFormValid"
>
Отправить форму
</button>
</form>
</template>
<script>
export default {
data() {
return {
passportData: null,
name: null,
passportDate: null,
};
},
computed: {
// Проверяем, что каждое поле формы валидно
isFormValid () {
return Object.keys(this.fields).every(field => this.fields[field].valid);
},
},
};
</script>
Как можно было заметить, при использовании флагов внутри fields.passport_date пришлось проверить поле passport_date на присутствие — во время первого рендера у vee-validate нет информации о ваших полях и, соответственно, нет объекта с флагами. Чтобы избавиться от этой проверки, используйте специальный хелпер mapFields [9].
Новый способ валидации, который появился в конце прошлого года — использование компонентов. Сами авторы рекомендуют использовать этот подход, и он отлично сочетается с новым синтаксисом слотов из Vue.js@2.6.
Библиотека предоставляет два компонента:
Вот форма из прошлого примера, но написанная с помощью компонентов:
<template>
<!--
ValidationObserver передает дефолтному слоту все флаги валидаций.
В примере используется только флаг valid для стилизации кнопки отправки формы
-->
<ValidationObserver v-slot="{ valid }">
<form @submit.prevent="doAction()">
<!--
ValidationProvider добавляет обработчики на все поля ввода внутри слота
и передает слоту все ошибки и флаги валидации
name - атрибут, который будет отображаться в ошибках
-->
<ValidationProvider
name="серия и номер паспорта"
:rules="{ required: true, regex: /^d{4} d{6}$/ }"
v-slot="{ errors }"
>
<input type="text" v-model="passportData">
<span v-if="errors[0]">
{{ errors[0] }}
</span>
</ValidationProvider>
<!--
По дефолту валидация данных сработает сразу после события input,
для валидации после blur или change используйте mode="lazy" или mode="eager"
-->
<ValidationProvider
name="дата выдачи паспорта"
:rules="{ required: true, date_format: 'dd.MM.yyyy' }"
v-slot="{ errors }"
mode="lazy"
>
<input type="text" v-model="passportDate">
<span v-if="errors[0]">
{{ errors[0] }}
</span>
</ValidationProvider>
<!--
Если хотите отобразить сразу все ошибки поля,
то используйте входной параметр :bails="false"
-->
<ValidationProvider
name="имя"
:rules="{ required: true, alpha: true, max: 10 }"
v-slot="{ errors }"
:bails="false"
>
<input type="text" v-model="name">
<span
v-for="error in errors"
:key="error"
>
{{ error }}
</span>
</ValidationProvider>
<button type="submit" :disabled="!valid">
Отправить форму
</button>
</form>
</ValidationObserver>
</template>
<script>
import { ValidationProvider, ValidationObserver } from 'vee-validate';
export default {
components: {
ValidationProvider,
ValidationObserver,
},
data() {
return {
passportData: null,
passportDate: null,
name: null,
};
},
};
</script>
Песочница с примером [10]
Первая и самая большая проблема — это, конечно же, ее размер. Библиотека не поддерживает tree-shaking и выборочное добавление валидаторов. Хотите вы или нет, в вашем банде всегда будет присутствовать 2 компонента и директива валидаций, перевод на английский язык и 35 валидаторов.
Вторая проблема — из-за подхода, основанного на подписке на события, могут возникать проблемы при интеграции с другими библиотеками, которые тоже изменяют поведение полей ввода (маскеры и т.п.).
Третья проблема — более субъективная — переводы ошибок общие и некрасивые, не отражают реальной сути.
Возьмем форму из прошлых примеров. Если вы введете неправильный номер и дату паспорта, то получите такие ошибки:
Поле серия и номер паспорта имеет ошибочный формат.
Поле дата выдачи паспорта должно быть в формате dd.MM.yyyy.
Хочется заменить их на что-то более удобочитаемое:
Поле серия и номер паспорта должно быть в формате 1234 567890
Поле дата выдачи паспорта должно быть в формате ДД.ММ.ГГГГ
Библиотека Vuelidate появилась в ответ на проблемы с подходами, которые содержит в себе библиотека vee-validate. Vuelidate не имеет ни обработчиков событий для полей, ни переводов для ошибок валидации.
Она требует от вас только одного — описать валидации в объекте validations. Затем она сама создаст computed-поле $v с флагами валидации полей и функциями для изменения этих флагов.
Благодаря простому подходу к валидациям, Vuelidate весит всего 3.4 КБайта (minified+GZIP), почти в 10 раз меньше vee-validate.
Устанавливается она так же просто:
npm i vuelidate
/* main.js */
import Vue from 'vue'
import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)
/* ... */
Перепишем форму из прошлого примера с использованием Vuelidate:
<template>
<form @submit.prevent="someAction()">
<!--
Ошибка будет отображена пользователю сразу же,
флаг $v.passportData.$invalid говорит о том, валидное поле или нет
-->
<div>
<input type="text" v-model="passportData">
<span v-if="$v.passportData.$invalid">
Серия и номер паспорта должны быть в формате 1234 567890
</span>
</div>
<!--
Ошибка будет отображена после события blur
Метод $touch() выставит флагу $v.passportDate.$dirty значение true.
Флаг $v.passportDate.$error высчитывается как
$v.passportDate.$invalid && $v.passportDate.$dirty
-->
<div>
<input type="text" v-model="passportDate" @blur="$v.passportDate.$touch()">
<span v-if="$v.passportDate.$error">
Дата должна быть в формате ДД.ММ.ГГГГ
</span>
</div>
<!--
Поле, которое тоже выведет ошибку после события blur, но с другим подходом
$v.passportDate.$model - объект, при записи данных в который:
- Vuelidate присвоит переданное значение полю passportDate
- Vuelidate вызовет метод $touch() у объекта $v.passportDate
Модификатор lazy необходим, чтобы присваивание произошло только после blur
-->
<div>
<input type="text" v-model.lazy="$v.passportDate.$model">
<span v-if="$v.passportDate.$error">
Дата должна быть в формате ДД.ММ.ГГГГ
</span>
</div>
<!-- Поле с несколькими ошибками -->
<div>
<input type="text" v-model="name" @blur="$v.name.$touch()">
<span v-if="$v.name.$error">
<template v-if="!$v.name.maxLength">
Длина имени не должна превышать {{ $v.name.$params.maxLength.max }} символов
</template>
<template v-else-if="!$v.name.alpha">
Имя должно содержать только буквы
</template>
<template v-else>
Имя обязательно для заполнения
</template>
</span>
</div>
<button type="submit" :disabled="$v.$invalid">
Отправить форму
</button>
</form>
</template>
<script>
import { required, maxLength } from 'vuelidate/lib/validators';
import moment from 'moment';
export default {
data() {
return {
passportData: null,
name: null,
passportDate: null,
};
},
// Модель для валидации, которую Vuelidate превратит в computed-поле $v
validations: {
// Название поля должно совпадать с полем в data
passportData: {
required,
validFormat: val => /^d{4} d{6}$/.test(val),
},
passportDate: {
required,
validDate: val => moment(val, 'DD.MM.YYYY', true).isValid(),
},
name: {
required,
maxLength: maxLength(10),
alpha: val => /^[а-яё]*$/i.test(val),
},
},
};
</script>
Песочница с примером [18]
Кастомные валидаторы можно быстро и легко описывать с помощью функций. Если вы хотите, чтобы параметры вашего валидатора попали в объект $params, используйте специальный хелпер withParams [19].
Как можно заметить, поля с несколькими ошибками занимают очень много места и выглядят монструозно — решается это созданием отдельного компонента для отображения валидаций.
Из проблем можно выделить разве что сравнительно скудное количество валидаторов из коробки, зачастую приходится писать свои функции для валидации.
Библиотеку Vuelidate я использовал во многих проектах и ни разу не сталкивался с нерешаемыми проблемами. Для меня она остается лучшим выбором. Если вы заботитесь о размере своего кода и предпочитаете модельный подход к описанию валидаций — берите ее, простая документация и богатое апи позволит валидировать формы любой сложности.
Если вы делаете админку для внутреннего использования, не хотите тратить ни капли времени на строчки ошибок — выбирайте vee-validate. Она поможет быстро и без проблем написать много валидаций.
Спасибо за внимание!
Автор: AndreasCag
Источник [22]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/312756
Ссылки в тексте:
[1] Vuelidate: https://github.com/vuelidate/vuelidate
[2] новых атрибутов для полей и Validation API: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation
[3] Песочница с примером: https://codesandbox.io/s/0m2p8700qv?fontsize=14&module=%2Fsrc%2FApp.vue
[4] Песочница с примером: https://codesandbox.io/s/qx4w19vlzq?fontsize=14&module=%2Fsrc%2FApp.vue
[5] GitHub: https://github.com/baianat/vee-validate
[6] документация: https://baianat.github.io/vee-validate/
[7] документация: https://vuelidate.netlify.com/
[8] Песочница с примером: https://codesandbox.io/s/qr57or6pj?fontsize=14&module=%2Fsrc%2FApp.vue
[9] mapFields: https://baianat.github.io/vee-validate/guide/flags.html#mapfields-helper
[10] Песочница с примером: https://codesandbox.io/s/zlr3kor81x?fontsize=14&module=%2Fsrc%2FApp.vue
[11] Интеграция с VueI18n: https://baianat.github.io/vee-validate/guide/localization.html#vuei18n-integration
[12] Автодобавление классов для стилизации: https://baianat.github.io/vee-validate/guide/classes-and-styling.html#auto-css-classes
[13] Поддержка :valid и :invlaid псевдоклассов: https://baianat.github.io/vee-validate/guide/classes-and-styling.html#styling-with-the-html5-validation-api
[14] Поддержка aria-* атрибутов : https://baianat.github.io/vee-validate/guide/classes-and-styling.html#styling-with-the-aria-attributes
[15] Поддержка HTML5 атрибутов для валидации : https://baianat.github.io/vee-validate/guide/inferred-rules.html#example
[16] Создание кастомных валидаторов : https://baianat.github.io/vee-validate/guide/custom-rules.html#creating-a-custom-rule
[17] Поддержка асинхронных валидаций: https://baianat.github.io/vee-validate/examples/async-backend-validation.html
[18] Песочница с примером: https://codesandbox.io/embed/y3no19zzz?fontsize=14&module=%2Fsrc%2FApp.vue
[19] withParams: https://vuelidate.netlify.com/#sub-props-support
[20] Поддержка асинхронных валидаций: https://vuelidate.netlify.com/#sub-asynchronous-validation
[21] Поддержка валидаций коллекций: https://vuelidate.netlify.com/#sub-collections-validation
[22] Источник: https://habr.com/ru/post/444900/?utm_campaign=444900
Нажмите здесь для печати.