Перейти к содержимому

10. routeLoader$ и routeAction$

В Qwik City загрузка данных и мутации управляются через routeLoader$ и routeAction$. Эти функции выполняются на сервере и обеспечивают типобезопасный обмен данными между сервером и клиентом.

routeLoader$ запускается на сервере перед рендером страницы и предоставляет данные компонентам:

src/routes/blog/[slug]/index.tsx
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 — они будут доступны всем дочерним страницам:

src/routes/layout.tsx
export const useCurrentUser = routeLoader$(async ({ cookie }) => {
return await getUserFromCookie(cookie.get('auth')?.value);
});
// src/routes/dashboard/index.tsx
export default component$(() => {
const user = useCurrentUser(); // Доступен из лейаута!
return <div>Привет, {user.value?.name}</div>;
});

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>
);
});
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, 'Слишком короткое имя'),
})
);
routeLoader$routeAction$
КогдаДо рендераПри отправке формы
HTTPGETPOST, PUT, DELETE
Данныеparams, queryFormData, JSON
КешДаНет
Ре-рендерАвтоматическиПо необходимости