routeLoader$()
路由加载器在服务器上加载数据,以便在 Qwik 组件内部使用。它们在 SPA/MPA 导航发生时触发,因此可以在渲染过程中由 Qwik 组件调用。
路由加载器只能在 src/routes
文件夹中的 layout.tsx
或 index.tsx
文件中声明,并且必须导出。
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useProductDetails = routeLoader$(async (requestEvent) => {
// 此代码仅在服务器上运行,每次导航后执行
const res = await fetch(`https://.../products/${requestEvent.params.productId}`);
const product = await res.json();
return product as Product;
});
export default component$(() => {
// 要在 Qwik 组件中访问 `routeLoader$` 数据,需要调用该钩子。
const signal = useProductDetails(); // Readonly<Signal<Product>>
return <p>产品名称:{signal.value.product.name}</p>;
});
路由加载器非常适合从数据库或 API 获取数据。例如,您可以使用它们从 CMS、天气 API 或数据库中获取用户列表等数据。
您不应该使用
routeLoader$
创建 REST API,对此,最好使用 Endpoint,它允许您对响应头和正文具有严格的控制。
routeLoader$
多个 整个应用程序中允许使用多个 routeLoader$
,它们可以在任何 Qwik 组件中使用。您甚至可以在同一个文件中声明多个 routeLoader$
。
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { Footer } from '../components/footer.tsx';
export const useProductData = routeLoader$(async () => {
const res = await fetch('https://.../product');
const product = (await res.json()) as Product;
return product;
});
export default component$(() => {
const signal = useProductData();
return (
<main>
<Slot />
<Footer />
</main>
);
});
import { component$ } from '@builder.io/qwik';
// 从 layout 中导入加载器
import { useProductData } from '../routes/layout.tsx';
export const Footer = component$(() => {
// 使用加载器数据
const signal = useProductData();
return <footer>产品名称:{signal.value.product.name}</footer>;
});
上面的示例展示了在不同文件中的两个不同组件中使用 useProductData()
。这是有意为之的行为。
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useLoginStatus = routeLoader$(async ({ cookie }) => {
return {
isUserLoggedIn: checkCookie(cookie),
};
});
export const useCurrentUser = routeLoader$(async ({ cookie }) => {
return {
user: currentUserFromCookie(cookie),
};
});
export default component$(() => {
const loginStatus = useLoginStatus();
const currentUser = useCurrentUser();
return (
<section>
<h1>管理员</h1>
{loginStatus.value.isUserLoggedIn ? (
<p>欢迎 {currentUser.value.user.name}</p>
) : (
<p>您尚未登录</p>
)}
</section>
);
});
上面的示例展示了在同一个文件中使用两个 routeLoader$
。一个通用的 useLoginStatus
加载器用于检查用户是否已登录,而一个更具体的 useCurrentUser
加载器用于检索用户数据。
RequestEvent
与 middleware 或 endpoint 的 onRequest
和 onGet
一样,routeLoader$
具有访问 RequestEvent
API 的能力,其中包含有关当前 HTTP 请求的信息。
当加载器需要根据请求条件返回数据,或者需要手动覆盖响应状态、头部或正文时,这些信息非常有用。
import { routeLoader$ } from '@builder.io/qwik-city';
export const useProductRecommendations = routeLoader$(async (requestEvent) => {
console.log('请求头部:', requestEvent.request.headers);
console.log('请求 cookie:', requestEvent.cookie);
console.log('请求 URL:', requestEvent.url);
console.log('请求方法:', requestEvent.method);
console.log('请求参数:', requestEvent.params);
// 使用请求详细信息获取个性化数据
const res = fetch(`https://.../recommendations?user=${requestEvent.params.user}`);
const recommendedProducts = (await res.json()) as Product[];
return recommendedProducts;
});
routeLoader$
中访问 routeLoader$
数据
在另一个 您可以使用 requestEvent.resolveValue
方法在另一个 routeLoader$
中访问一个 routeLoader$
的数据。
import { routeLoader$ } from '@builder.io/qwik-city';
export const useProductDetails = routeLoader$(async (requestEvent) => {
const res = await fetch(`https://.../products/${requestEvent.params.productId}`);
const product = await res.json();
return product;
});
export const useProductRecommendations = routeLoader$(async (requestEvent) => {
// 从其他加载器中解析产品详细信息
const product = await requestEvent.resolveValue(useProductDetails);
// 使用产品详细信息获取个性化数据
const res = fetch(`https://.../recommendations?product=${product.id}`);
const recommendedProducts = (await res.json()) as Product[];
return recommendedProducts;
});
相同的 API 可用于访问
routeAction$
或globalAction$
中的数据。
routeLoader$
的失败值
使用 routeLoader$
可以使用 fail
方法返回一个失败值,这是一个特殊的值,表示加载器未能成功加载预期的数据。
此外,fail
函数允许 routeLoader$
覆盖 HTTP 状态码,例如返回 404。
当加载器需要返回一个不是 undefined
的“错误”值,但又需要指示数据加载失败时,这非常有用。
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
export const useProductDetails = routeLoader$(async (requestEvent) => {
const product = await db.from('products').filter('id', 'eq', requestEvent.params.productId);
if (!product) {
// 返回一个失败值,表示未找到产品
return requestEvent.fail(404, {
errorMessage: '未找到产品'
});
}
return {
productName: product.name
};
});
export default component$(() => {
const product = useProductDetails();
if (product.value.errorMessage) {
// 渲染失败值的 UI
return <div>{product.value.errorMessage}</div>;
}
return <div>产品名称:{product.value.productName}</div>;
});
处理加载器中的相对 URL
在服务器端执行环境中,将相对 URL 转换为绝对 URL 是非常重要的,以确保其正常功能。可以通过使用 useLocation()
函数的 origin
前缀来实现。
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
export default component$(() => {
const location = useLocation();
const relativeUrl = '/mock-data';
const absoluteUrl = location.url.origin + relativeUrl;
return (
<section>
<div>相对 URL:{relativeUrl}</div>
<div>绝对 URL:{absoluteUrl}</div>
</section>
);
});
性能考虑
路由加载器在服务器上,在每次导航后执行。这意味着无论用户导航到 SPA 或 MPA 中的哪个页面,它们都会执行,并且即使用户导航到同一个页面,它们也会执行。
加载器在 Qwik 中间件处理程序(onRequest
、onGet
、onPost
等)之后执行,Qwik 组件渲染之前执行。这使得加载器能够尽早开始获取数据,从而减少延迟。