В Vue форма строится на связке v-model + реактивное состояние + обработчик отправки. Модификатор @submit.prevent блокирует стандартное поведение браузера (перезагрузку страницы):
<template>
<form @submit.prevent="handleSubmit">
<input v-model.trim="form.name" type="text" placeholder="Имя">
<input v-model.trim="form.email" type="email" placeholder="Email">
<input v-model.number="form.age" type="number" placeholder="Возраст">
<button type="submit">Отправить</button>
</form>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({ name: '', email: '', age: 0 })
function handleSubmit() {
console.log('Данные формы:', { ...form })
}
</script>Храним ошибки в отдельном реактивном объекте:
<script setup>
import { reactive, computed } from 'vue'
const form = reactive({ name: '', email: '', password: '' })
const errors = reactive({ name: '', email: '', password: '' })
const touched = reactive({ name: false, email: false, password: false })
</script>Валидацию удобно описывать через computed-свойства:
<script setup>
import { reactive, computed } from 'vue'
const form = reactive({ name: '', email: '', password: '' })
const validationRules = computed(() => ({
name: [
{ test: form.name.length >= 2, message: 'Имя должно быть не менее 2 символов' },
{ test: /^[а-яёА-ЯЁa-zA-Z\s]+$/.test(form.name), message: 'Только буквы' },
],
email: [
{ test: form.email.includes('@'), message: 'Введите корректный email' },
{ test: form.email.length > 0, message: 'Email обязателен' },
],
password: [
{ test: form.password.length >= 8, message: 'Минимум 8 символов' },
{ test: /[0-9]/.test(form.password), message: 'Должна содержать цифры' },
],
}))
const fieldErrors = computed(() => {
const result = {}
for (const [field, rules] of Object.entries(validationRules.value)) {
const failed = rules.find(rule => !rule.test)
result[field] = failed ? failed.message : ''
}
return result
})
const isFormValid = computed(() =>
Object.values(fieldErrors.value).every(err => err === '')
)
</script>Не стоит показывать ошибки сразу при открытии формы. Используем флаг touched:
<template>
<input
v-model.trim="form.name"
@blur="touched.name = true"
:class="{ 'is-error': touched.name && fieldErrors.name }"
>
<p v-if="touched.name && fieldErrors.name" class="error-msg">
{{ fieldErrors.name }}
</p>
</template>function resetForm() {
Object.assign(form, { name: '', email: '', password: '' })
Object.assign(touched, { name: false, email: false, password: false })
}const isSubmitting = ref(false)
const submitError = ref('')
async function handleSubmit() {
// Помечаем все поля как touched для показа ошибок
Object.keys(touched).forEach(key => { touched[key] = true })
if (!isFormValid.value) return
isSubmitting.value = true
submitError.value = ''
try {
await api.submitForm({ ...form })
resetForm()
console.log('Форма успешно отправлена!')
} catch (err) {
submitError.value = err.message
} finally {
isSubmitting.value = false
}
}Полная система валидации форм: правила, ошибки, touched-флаги и обработка отправки
// Реализуем систему валидации форм — аналог того что делает Vue через reactive + computed
// Движок валидации — принимает данные и описание правил
function createValidator(rules) {
return function validate(data) {
const errors = {}
let isValid = true
for (const [field, fieldRules] of Object.entries(rules)) {
const value = data[field] ?? ''
let fieldError = ''
for (const rule of fieldRules) {
const passed = rule.test(value, data)
if (!passed) {
fieldError = rule.message
break // первая ошибка поля
}
}
errors[field] = fieldError
if (fieldError) isValid = false
}
return { errors, isValid }
}
}
// Создаём правила для формы регистрации
const registrationRules = {
username: [
{ test: v => v.length >= 3, message: 'Имя пользователя: минимум 3 символа' },
{ test: v => v.length <= 20, message: 'Имя пользователя: максимум 20 символов' },
{ test: v => /^[a-zA-Z0-9_]+$/.test(v), message: 'Только латиница, цифры и _' },
],
email: [
{ test: v => v.length > 0, message: 'Email обязателен' },
{ test: v => v.includes('@') && v.includes('.'), message: 'Некорректный email' },
],
password: [
{ test: v => v.length >= 8, message: 'Пароль: минимум 8 символов' },
{ test: v => /[A-Z]/.test(v), message: 'Пароль должен содержать заглавную букву' },
{ test: v => /[0-9]/.test(v), message: 'Пароль должен содержать цифру' },
],
passwordConfirm: [
{ test: v => v.length > 0, message: 'Подтвердите пароль' },
{ test: (v, data) => v === data.password, message: 'Пароли не совпадают' },
],
}
const validate = createValidator(registrationRules)
// Эмуляция состояния формы и touched-флагов
let formData = { username: '', email: '', password: '', passwordConfirm: '' }
let touched = { username: false, email: false, password: false, passwordConfirm: false }
function getVisibleErrors(formData, touched) {
const { errors } = validate(formData)
const visible = {}
for (const field of Object.keys(errors)) {
visible[field] = touched[field] ? errors[field] : ''
}
return visible
}
// === Симуляция пользовательского ввода ===
console.log('=== Состояние формы ===\n')
console.log('1. Пустая форма (пользователь ещё ничего не трогал):')
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
// Все пустые — touched = false
console.log('\n2. Пользователь тронул поле username и ничего не ввёл:')
touched.username = true
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
// username: 'Имя пользователя: минимум 3 символа'
console.log('\n3. Ввёл короткое имя "ab":')
formData.username = 'ab'
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
console.log('\n4. Ввёл корректное имя "alexey_123":')
formData.username = 'alexey_123'
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
// username: '' (нет ошибки)
console.log('\n5. Полностью заполненная корректная форма:')
formData = {
username: 'alexey_123',
email: 'alexey@example.com',
password: 'SecurePass1',
passwordConfirm: 'SecurePass1',
}
touched = { username: true, email: true, password: true, passwordConfirm: true }
const { errors, isValid } = validate(formData)
console.log(' Ошибки:', errors)
console.log(' Форма валидна:', isValid)
console.log('\n6. Пароли не совпадают:')
formData.passwordConfirm = 'WrongPass'
const result = validate(formData)
console.log(' passwordConfirm error:', result.errors.passwordConfirm)
console.log(' Форма валидна:', result.isValid)В Vue форма строится на связке v-model + реактивное состояние + обработчик отправки. Модификатор @submit.prevent блокирует стандартное поведение браузера (перезагрузку страницы):
<template>
<form @submit.prevent="handleSubmit">
<input v-model.trim="form.name" type="text" placeholder="Имя">
<input v-model.trim="form.email" type="email" placeholder="Email">
<input v-model.number="form.age" type="number" placeholder="Возраст">
<button type="submit">Отправить</button>
</form>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({ name: '', email: '', age: 0 })
function handleSubmit() {
console.log('Данные формы:', { ...form })
}
</script>Храним ошибки в отдельном реактивном объекте:
<script setup>
import { reactive, computed } from 'vue'
const form = reactive({ name: '', email: '', password: '' })
const errors = reactive({ name: '', email: '', password: '' })
const touched = reactive({ name: false, email: false, password: false })
</script>Валидацию удобно описывать через computed-свойства:
<script setup>
import { reactive, computed } from 'vue'
const form = reactive({ name: '', email: '', password: '' })
const validationRules = computed(() => ({
name: [
{ test: form.name.length >= 2, message: 'Имя должно быть не менее 2 символов' },
{ test: /^[а-яёА-ЯЁa-zA-Z\s]+$/.test(form.name), message: 'Только буквы' },
],
email: [
{ test: form.email.includes('@'), message: 'Введите корректный email' },
{ test: form.email.length > 0, message: 'Email обязателен' },
],
password: [
{ test: form.password.length >= 8, message: 'Минимум 8 символов' },
{ test: /[0-9]/.test(form.password), message: 'Должна содержать цифры' },
],
}))
const fieldErrors = computed(() => {
const result = {}
for (const [field, rules] of Object.entries(validationRules.value)) {
const failed = rules.find(rule => !rule.test)
result[field] = failed ? failed.message : ''
}
return result
})
const isFormValid = computed(() =>
Object.values(fieldErrors.value).every(err => err === '')
)
</script>Не стоит показывать ошибки сразу при открытии формы. Используем флаг touched:
<template>
<input
v-model.trim="form.name"
@blur="touched.name = true"
:class="{ 'is-error': touched.name && fieldErrors.name }"
>
<p v-if="touched.name && fieldErrors.name" class="error-msg">
{{ fieldErrors.name }}
</p>
</template>function resetForm() {
Object.assign(form, { name: '', email: '', password: '' })
Object.assign(touched, { name: false, email: false, password: false })
}const isSubmitting = ref(false)
const submitError = ref('')
async function handleSubmit() {
// Помечаем все поля как touched для показа ошибок
Object.keys(touched).forEach(key => { touched[key] = true })
if (!isFormValid.value) return
isSubmitting.value = true
submitError.value = ''
try {
await api.submitForm({ ...form })
resetForm()
console.log('Форма успешно отправлена!')
} catch (err) {
submitError.value = err.message
} finally {
isSubmitting.value = false
}
}Полная система валидации форм: правила, ошибки, touched-флаги и обработка отправки
// Реализуем систему валидации форм — аналог того что делает Vue через reactive + computed
// Движок валидации — принимает данные и описание правил
function createValidator(rules) {
return function validate(data) {
const errors = {}
let isValid = true
for (const [field, fieldRules] of Object.entries(rules)) {
const value = data[field] ?? ''
let fieldError = ''
for (const rule of fieldRules) {
const passed = rule.test(value, data)
if (!passed) {
fieldError = rule.message
break // первая ошибка поля
}
}
errors[field] = fieldError
if (fieldError) isValid = false
}
return { errors, isValid }
}
}
// Создаём правила для формы регистрации
const registrationRules = {
username: [
{ test: v => v.length >= 3, message: 'Имя пользователя: минимум 3 символа' },
{ test: v => v.length <= 20, message: 'Имя пользователя: максимум 20 символов' },
{ test: v => /^[a-zA-Z0-9_]+$/.test(v), message: 'Только латиница, цифры и _' },
],
email: [
{ test: v => v.length > 0, message: 'Email обязателен' },
{ test: v => v.includes('@') && v.includes('.'), message: 'Некорректный email' },
],
password: [
{ test: v => v.length >= 8, message: 'Пароль: минимум 8 символов' },
{ test: v => /[A-Z]/.test(v), message: 'Пароль должен содержать заглавную букву' },
{ test: v => /[0-9]/.test(v), message: 'Пароль должен содержать цифру' },
],
passwordConfirm: [
{ test: v => v.length > 0, message: 'Подтвердите пароль' },
{ test: (v, data) => v === data.password, message: 'Пароли не совпадают' },
],
}
const validate = createValidator(registrationRules)
// Эмуляция состояния формы и touched-флагов
let formData = { username: '', email: '', password: '', passwordConfirm: '' }
let touched = { username: false, email: false, password: false, passwordConfirm: false }
function getVisibleErrors(formData, touched) {
const { errors } = validate(formData)
const visible = {}
for (const field of Object.keys(errors)) {
visible[field] = touched[field] ? errors[field] : ''
}
return visible
}
// === Симуляция пользовательского ввода ===
console.log('=== Состояние формы ===\n')
console.log('1. Пустая форма (пользователь ещё ничего не трогал):')
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
// Все пустые — touched = false
console.log('\n2. Пользователь тронул поле username и ничего не ввёл:')
touched.username = true
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
// username: 'Имя пользователя: минимум 3 символа'
console.log('\n3. Ввёл короткое имя "ab":')
formData.username = 'ab'
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
console.log('\n4. Ввёл корректное имя "alexey_123":')
formData.username = 'alexey_123'
console.log(' Видимые ошибки:', getVisibleErrors(formData, touched))
// username: '' (нет ошибки)
console.log('\n5. Полностью заполненная корректная форма:')
formData = {
username: 'alexey_123',
email: 'alexey@example.com',
password: 'SecurePass1',
passwordConfirm: 'SecurePass1',
}
touched = { username: true, email: true, password: true, passwordConfirm: true }
const { errors, isValid } = validate(formData)
console.log(' Ошибки:', errors)
console.log(' Форма валидна:', isValid)
console.log('\n6. Пароли не совпадают:')
formData.passwordConfirm = 'WrongPass'
const result = validate(formData)
console.log(' passwordConfirm error:', result.errors.passwordConfirm)
console.log(' Форма валидна:', result.isValid)Реализуй функцию `createFormValidator(rules)`, которая принимает объект с правилами (каждое правило — объект `{ test: (value) => boolean, message: string }`) и возвращает объект с методами: `validate(data)` — возвращает `{ errors: {}, isValid: boolean }`, `getFirstError(data, field)` — возвращает строку с первой ошибкой поля или пустую строку, `isFieldValid(data, field)` — возвращает `true` если у поля нет ошибок.
В `validate` используй вложенный цикл: `for (const [field, fieldRules] of Object.entries(rules))`, внутри — `for (const rule of fieldRules)`. Для `isValid` используй `Object.values(errors).every(e => e === "")`. В `getFirstError` вызывай `validate(data).errors[field] ?? ""`.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке