10. routeLoader$ и routeAction$
В Qwik City загрузка данных и мутации управляются через routeLoader$ и routeAction$. Эти функции выполняются на сервере и обеспечивают типобезопасный обмен данными между сервером и клиентом.
routeLoader$ — загрузка данных
Заголовок раздела «routeLoader$ — загрузка данных»routeLoader$ запускается на сервере перед рендером страницы и предоставляет данные компонентам:
import { component$ } from '@builder.io/qwik';import { routeLoader$ } from '@builder.io/qwik-city';
// Определяем загрузчикexport const usePost = routeLoader$(async ({ params, status }) => { const post = await db.posts.findUnique({ where: { slug: params.slug } });
if (!post) { status(404); // Устанавливаем HTTP статус return null; }
return post;});
// Используем в компонентеexport default component$(() => { const post = usePost(); // Данные уже загружены!
if (!post.value) { return <h1>Пост не найден</h1>; }
return ( <article> <h1>{post.value.title}</h1> <p>{post.value.content}</p> </article> );});Доступные параметры загрузчика
Заголовок раздела «Доступные параметры загрузчика»export const useData = routeLoader$(async ({ params, // Параметры маршрута (/blog/[slug]) query, // Параметры запроса (?page=1) request, // Request объект cookie, // Cookies env, // Переменные окружения platform, // Платформа (Cloudflare, Vercel...) status, // Установка HTTP статуса redirect, // Перенаправление fail, // Возврат ошибки}) => { return { data: 'value' };});Загрузка в лейауте
Заголовок раздела «Загрузка в лейауте»Загрузчики можно определять в layout.tsx — они будут доступны всем дочерним страницам:
export const useCurrentUser = routeLoader$(async ({ cookie }) => { return await getUserFromCookie(cookie.get('auth')?.value);});
// src/routes/dashboard/index.tsxexport default component$(() => { const user = useCurrentUser(); // Доступен из лейаута! return <div>Привет, {user.value?.name}</div>;});routeAction$ — мутации данных
Заголовок раздела «routeAction$ — мутации данных»routeAction$ обрабатывает форм-данные и другие мутации на сервере:
import { routeAction$, Form } from '@builder.io/qwik-city';
export const useCreatePost = routeAction$(async (data, { redirect }) => { // data автоматически извлечён из FormData const post = await db.posts.create({ data: { title: data.title as string, content: data.content as string, } });
throw redirect(302, \`/blog/\${post.slug}\`);});
export default component$(() => { const createPost = useCreatePost();
return ( <Form action={createPost}> <input name="title" placeholder="Заголовок" /> <textarea name="content" /> <button type="submit">Опубликовать</button> {createPost.value?.failed && ( <p>Ошибка: {createPost.value.message}</p> )} </Form> );});Валидация с zod
Заголовок раздела «Валидация с zod»import { routeAction$, zod$, z } from '@builder.io/qwik-city';
export const useSignUp = routeAction$( async (data) => { // data уже валидирован и типизирован! await createUser(data.email, data.password); return { success: true }; }, zod$({ email: z.string().email('Неверный email'), password: z.string().min(8, 'Минимум 8 символов'), name: z.string().min(2, 'Слишком короткое имя'), }));useLoader vs useAction: разница
Заголовок раздела «useLoader vs useAction: разница»| routeLoader$ | routeAction$ | |
|---|---|---|
| Когда | До рендера | При отправке формы |
| HTTP | GET | POST, PUT, DELETE |
| Данные | params, query | FormData, JSON |
| Кеш | Да | Нет |
| Ре-рендер | Автоматически | По необходимости |