Middle: питання на співбесіді з теми «Inertia й Laravel»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Деякі дані потрібні майже на кожній сторінці: поточний користувач у шапці, назва застосунку, налаштування. Передавати їх у кожному контролері незручно - для цього є спільні дані в middleware HandleInertiaRequests:
class HandleInertiaRequests extends Middleware
{
public function share(Request $request): array
{
return [
...parent::share($request),
'appName' => config('app.name'),
'auth' => [
'user' => fn () => $request->user()?->only('id', 'name', 'avatar_url'),
],
];
}
}
<script setup>
import { computed } from 'vue';
import { usePage } from '@inertiajs/vue3';
const page = usePage();
const user = computed(() => page.props.auth.user);
</script>
Спільні дані зливаються з props сторінки - тож простір імен (auth.user) захищає від зіткнень із props контролерів.
Головні правила:
- спільні дані йдуть з кожною відповіддю - кожен перехід і кожне часткове перезавантаження. Важкий запит тут - податок на всі сторінки. Замикання (
fn () => ...) обчислюються лише тоді, коли prop справді потрібен; - рідко змінні дані (список країн, налаштування) -
Inertia::once()чи методshareOnce(): клієнт запам'ятовує значення й не отримує його з кожною відповіддю; - усе видно в браузері.
$request->user()безonly()віддасть усі атрибути моделі в JSON кожної сторінки - email, телефон, службові прапорці, усе, що не в$hidden. Передавайте явний перелік полів чи ресурс; - ролі й права - готовими булевими значеннями для інтерфейсу (
can.manageUsers), а не списком усіх дозволів системи.
Валідаційні помилки адаптер Laravel уже додає в спільні дані (errors), а флеш-дані в Inertia 3 мають окремий механізм Inertia::flash().
Версія ассетів визначається в тому самому middleware (метод version()) - для Vite вона обчислюється автоматично.
У класичній SPA сервер на помилку валідації повертає 422 з JSON, а клієнт розкладає помилки по полях. Inertia працює інакше - так само, як звичайні форми Laravel:
- форма відправляється Inertia-запитом;
$request->validate()не проходить - Laravel перенаправляє назад і кладе помилки в сесію;- адаптер Inertia додає помилки з сесії в props сторінки (
errors); - Inertia бачить непорожній
page.props.errorsі вважає відправку невдалою - викликаєonError, а неonSuccess.
Відповідей 422 Inertia-застосунок не генерує взагалі.
public function store(Request $request)
{
$request->validate([
'email' => ['required', 'email', 'unique:users'],
'name' => ['required', 'max:100'],
]);
// ...
}
З useForm помилки потрапляють у form.errors автоматично. Без нього - через prop:
<script setup>
defineProps({ errors: Object });
</script>
<template>
<p v-if="errors.email">{{ errors.email }}</p>
</template>
Введене не губиться: після перенаправлення назад Inertia за замовчуванням зберігає стан компонента сторінки, тож поля не очищаються.
Error bags - для сторінки з кількома формами з однаковими назвами полів (дві форми з полем email):
router.post('/companies', data, { errorBag: 'createCompany' });
router.post('/users', data, { errorBag: 'createUser' });
// помилки прийдуть у page.props.errors.createCompany / .createUser
З useForm error bags не потрібні - помилки й так прив'язані до екземпляра форми.
Усі помилки поля, а не лише перша. За замовчуванням адаптер Laravel повертає по одній помилці на поле. Для всіх - protected $withAllErrors = true; у HandleInertiaRequests; тоді кожне поле містить масив рядків.
Часткові перезавантаження й помилки. errors передається завжди, тож порожній набір помилок з сервера затре клієнтські. Щоб зберегти помилки під час router.reload({ only: [...] }), у Inertia 3 є опція preserveErrors: true.
Попередня валідація (до відправки) - Laravel Precognition, підтримку якої вбудовано у форми Inertia.
Сторінка списку користувачів має props users і companies (для фільтра). Користувач змінює фільтр - потрібні нові users, а companies не змінилися. Отримувати й перераховувати все - марна робота.
Часткове перезавантаження - запит лише вибраних props тієї самої сторінки:
import { router } from '@inertiajs/vue3';
router.reload({ only: ['users'], data: { company: 3 } });
router.reload({ except: ['companies'] });
<Link href="/users?active=1" :only="['users']">Активні</Link>
Працює лише для візитів на той самий компонент сторінки.
Щоб сервер справді не виконував зайве - props мають бути замиканнями:
return Inertia::render('Users/Index', [
'users' => fn () => User::query()->filter(request()->all())->paginate(),
'companies' => fn () => Company::orderBy('name')->get(['id', 'name']),
]);
Без замикання Company::all() виконається на кожен запит, навіть якщо результат не потрапить у відповідь.
Типи props:
| Запис | Звичайний візит | Часткове перезавантаження | Обчислюється |
|---|---|---|---|
User::all() |
так | за запитом | завжди |
fn () => User::all() |
так | за запитом | лише коли потрібен |
Inertia::optional(fn () => ...) |
ні | за запитом | лише коли потрібен |
Inertia::always(...) |
так | завжди | завжди |
Inertia::optional()- prop, який сторінка отримує лише на явний запит (only): дані для вкладки, яку відкривають рідко. У Inertia 3 він замінив видаленийInertia::lazy();Inertia::always()- потрапляє навіть у часткові перезавантаження (так передаються помилки валідації).
Inertia 3: only/except підтримують крапкову нотацію для вкладених props (only: ['auth.notifications']), а optional, defer, merge працюють на будь-якій глибині масиву.
Поєднання з once(): Inertia::optional(fn () => ...)->once() - завантажити один раз і пам'ятати на клієнті між переходами.
Докладніше в документації: Inertia: часткові перезавантаження
Повідомлення «Користувача створено» має з'явитися один раз після дії й не повторюватися. Раніше в Inertia-застосунках його передавали через session()->flash() і спільні дані middleware. У Inertia 3 для цього є окремий механізм - флеш-дані.
На сервері:
use Inertia\Inertia;
public function store(StoreUserRequest $request)
{
$user = User::create($request->validated());
Inertia::flash('toast', ['type' => 'success', 'message' => 'Користувача створено']);
return back();
// або коротше: return Inertia::flash('newUserId', $user->id)->back();
}
flash() можна поєднувати і з render(): Inertia::render(...)->flash('highlight', $id).
На клієнті - у page.flash, окремо від props:
<script setup>
import { usePage } from '@inertiajs/vue3';
const page = usePage();
</script>
<template>
<div v-if="page.flash.toast" class="toast">{{ page.flash.toast.message }}</div>
</template>
Або реагувати подією - в одному місці макета:
router.on('flash', (event) => {
if (event.detail.flash.toast) showToast(event.detail.flash.toast);
});
router.post('/users', data, {
onFlash: ({ newUserId }) => highlight(newUserId),
});
Чим кращий за флеш через сесію в props:
- не потрапляє в історію браузера. Props зберігаються в стані історії: користувач натиснув «Назад» - і старе «Збережено» знову на екрані. Флеш-дані в історію не пишуться;
- не змішується з props сторінки;
- працює однаково з редиректом і без нього: middleware сам зберігає флеш у сесію при перенаправленні.
Що варто знати:
- обробники
router.on(...), зареєстровані в компонентах, треба знімати при демонтажі (функція, яку повертаєon), інакше в непостійних макетах вони накопичуються; - флеш - для одноразових повідомлень, а не для даних, які мають бути на сторінці після оновлення;
- як і props, флеш-дані видно в браузері - лише публічна інформація.