Junior: питання на співбесіді з теми «Форми й валідація»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Найпростіший підхід - один реактивний об'єкт з усіма полями форми і v-model на кожному полі:
<script setup lang="ts">
import { reactive, ref } from 'vue'
const initial = () => ({ name: '', email: '', role: 'editor', agree: false })
const form = reactive(initial())
const submitting = ref(false)
async function submit() {
submitting.value = true
try {
await api.post('/users', { ...form })
Object.assign(form, initial()) // скидання
} finally {
submitting.value = false
}
}
</script>
<template>
<form @submit.prevent="submit">
<input v-model.trim="form.name" />
<input v-model="form.email" type="email" />
<select v-model="form.role">
<option value="editor">Редактор</option>
<option value="admin">Адміністратор</option>
</select>
<input v-model="form.agree" type="checkbox" />
<button :disabled="submitting">Зберегти</button>
</form>
</template>
reactive чи ref:
reactive({...}) |
ref({...}) |
|
|---|---|---|
доступ у <script> |
form.email |
form.value.email |
| замінити весь об'єкт | ні - лише Object.assign |
так: form.value = initial() |
| деструктуризація | ламає реактивність | теж ламає (form.value - реактивний об'єкт) |
ref зручніший, коли форму треба повністю замінити (завантажили дані для редагування). reactive - коротший запис полів.
Модифікатори v-model: .trim прибирає пробели, .number перетворює на число, .lazy оновлює значення на change, а не на кожен введений символ.
Типові помилки:
@submitбез.prevent- браузер відправляє форму звичайним запитом і перезавантажує сторінку;- скидання через
form = initial()дляreactive- зміниться лише локальна змінна, а шаблон лишиться прив'язаним до старого об'єкта; - відправка самого реактивного проксі - бібліотеки зазвичай серіалізують його коректно, але безпечніше передавати звичайну копію (
{ ...form }чиtoRaw(form)); - checkbox для масиву: кілька чекбоксів з одним
v-modelі масивом у стані дають список вибраних значень; - немає захисту від подвійного кліку - кнопка має бути вимкнена на час відправки.
Для складних форм (валідація, вкладені масиви, стан «змінено») варто взяти бібліотеку форм, а не винаходити все самому.
Коли валідація Laravel не проходить у запиті, що очікує JSON, сервер відповідає статусом 422 і тілом:
{
"message": "The email has already been taken. (and 1 more error)",
"errors": {
"email": ["The email has already been taken."],
"items.0.qty": ["The items.0.qty field must be at least 1."]
}
}
Ключі - назви полів, для вкладених даних - через крапку. Значення - масив повідомлень.
Обробка в Vue:
<script setup lang="ts">
import { reactive, ref } from 'vue'
import axios from 'axios'
const form = reactive({ name: '', email: '' })
const errors = ref<Record<string, string[]>>({})
async function submit() {
errors.value = {}
try {
await axios.post('/api/users', form, { headers: { Accept: 'application/json' } })
} catch (e) {
if (axios.isAxiosError(e) && e.response?.status === 422) {
errors.value = e.response.data.errors
return
}
throw e // інші помилки - далі, до глобального обробника
}
}
</script>
<template>
<input v-model="form.email" :aria-invalid="!!errors.email" />
<p v-if="errors.email" class="error">{{ errors.email[0] }}</p>
</template>
З fetch код схожий, але fetch не кидає виняток на 422 - перевіряти треба response.status самостійно, а тіло читати через await response.json().
Важливі деталі:
- заголовок
Accept: application/jsonобов'язковий: без нього Laravel вважає запит звичайним переходом форми й відповідає редиректом назад з помилками в сесії, а не JSON; - очищайте помилки перед новою відправкою, інакше старі повідомлення лишаються на екрані;
- прибирайте помилку поля при його зміні -
watchна поле чи очищення в@input- користувач бачить, що виправлення прийнято; - вкладені ключі (
items.0.qty) треба зіставити з полем у списку - зберігайте помилки за тим самим шаблоном назв; - 401 і 419 - це не валідація: 401 - не автентифікований, 419 - прострочений CSRF-токен (для SPA на Sanctum спершу потрібен запит до
/sanctum/csrf-cookie).
Типізація: опишіть тип відповіді 422 ({ message: string; errors: Record<string, string[]> }) один раз і використовуйте в усіх формах - через спільну функцію чи composable useServerErrors().
Доступна форма - та, яку можна заповнити з клавіатури й зрозуміти з екранним читачем. Більшість потрібного дає правильний HTML, а Vue лише має не зламати його.
1. Мітка для кожного поля, пов'язана з полем:
<script setup>
import { useId } from 'vue'
const id = useId()
</script>
<template>
<label :for="id">Email</label>
<input :id="id" v-model="form.email" type="email" autocomplete="email" required />
</template>
Плейсхолдер - не мітка: він зникає при введенні й часто має низький контраст. useId() (Vue 3.5) генерує стабільний id, однаковий на сервері й у браузері при SSR.
2. Помилка, пов'язана з полем:
<input
:id="id"
v-model="form.email"
:aria-invalid="errors.email ? 'true' : undefined"
:aria-describedby="errors.email ? `${id}-error` : undefined"
/>
<p v-if="errors.email" :id="`${id}-error`">{{ errors.email[0] }}</p>
aria-invalidповідомляє, що значення некоректне;aria-describedby- екранний читач прочитає текст помилки разом з назвою поля;- помилка не повинна передаватися лише кольором (червона рамка) - потрібен текст.
3. Фокус на першому полі з помилкою після відправки:
import { nextTick } from 'vue'
async function onInvalid() {
await nextTick() // дочекатися рендеру повідомлень
document.querySelector<HTMLElement>('[aria-invalid="true"]')?.focus()
}
Без цього користувач клавіатури чи екранного читача натискає «Зберегти» - і нічого не відбувається з його точки зору.
4. Зведення помилок для довгих форм: блок з role="alert" нагорі зі списком помилок-посилань на поля.
Ще кілька правил:
<button type="submit">у<form>, а неdivз@click- тоді працює Enter і екранні читачі розуміють, що це форма;- групи перемикачів - у
<fieldset>з<legend>; autocomplete(email,new-password,one-time-code) - менеджери паролів і мобільні клавіатури;- стан відправки -
aria-busyчи текст на кнопці, а не лише спінер; - не вимикайте кнопку відправки до заповнення всіх полів: користувач не розуміє, чого бракує. Краще дозволити натиснути й показати помилки.
Перевірка: пройти форму лише з клавіатури (Tab, Enter, Space) і запустити axe DevTools чи Lighthouse.