routeLoader$()

路由加载器在服务器上加载数据,使其可在 Qwik 组件内部使用。它们在 SPA/MPA 导航发生时触发,因此可以在渲染期间由 Qwik 组件调用。

路由加载器只能在 src/routes 文件夹中声明,在 layout.tsxindex.tsx 文件中,并且必须导出。

如果要管理通用的可重用路由加载器,则必须从现有路由的 'layout.tsx' 或 'index.tsx' 文件中重新导出此函数,否则它将无法运行或抛出异常。有关更多信息,请查看菜谱

src/routes/product/[productId]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductDetails = routeLoader$(async (requestEvent) => {
  // This code runs only on the server, after every navigation
  const res = await fetch(`https://.../products/${requestEvent.params.productId}`);
  const product = await res.json();
  return product as Product;
});
 
export default component$(() => {
  // In order to access the `routeLoader$` data within a Qwik Component, you need to call the hook.
  const signal = useProductDetails(); // Readonly<Signal<Product>>
  return <p>Product name: {signal.value.product.name}</p>;
});

路由加载器非常适合从数据库或 API 获取数据。例如,您可以使用它们从 CMS、天气 API 或数据库中获取用户列表。

您不应该使用 routeLoader$ 创建 REST API,因为使用端点会更好,它允许您对响应头和主体进行严格控制。

多个 routeLoader$

在整个应用程序中允许使用多个 routeLoader$,并且它们可以在任何 Qwik 组件中使用。您甚至可以在同一个文件中声明多个 routeLoader$

src/routes/layout.tsx
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>
  );
});
src/components/footer.tsx
import { component$ } from '@builder.io/qwik';
 
// Import the loader from the layout
import { useProductData } from '../routes/layout.tsx';
 
export const Footer = component$(() => {
  // Consume the loader data
  const signal = useProductData();
  return <footer>Product name: {signal.value.product.name}</footer>;
});

上面的示例展示了在不同文件中的两个不同组件中使用 useProductData()。这是预期的行为。

src/routes/admin/index.tsx
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>Admin</h1>
      {loginStatus.value.isUserLoggedIn ? (
        <p>Welcome {currentUser.value.user.name}</p>
      ) : (
        <p>You are not logged in</p>
      )}
    </section>
  );
});

上面的示例展示了在同一个文件中使用两个 routeLoader$。一个通用的 useLoginStatus 加载器用于检查用户是否已登录,而一个更具体的 useCurrentUser 加载器用于检索用户数据。

RequestEvent

中间件端点onRequestonGet 一样,routeLoader$ 可以访问RequestEvent API,其中包含有关当前 HTTP 请求的信息。

当加载器需要根据请求有条件地返回数据,或者需要手动覆盖响应状态、头或主体时,此信息非常有用。

src/routes/product/[user]/index.tsx
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductRecommendations = routeLoader$(async (requestEvent) => {
  console.log('Request headers:', requestEvent.request.headers);
  console.log('Request cookies:', requestEvent.cookie);
  console.log('Request url:', requestEvent.url);
  console.log('Request method:', requestEvent.method);
  console.log('Request params:', requestEvent.params);
 
  // Use request details to fetch personalized data
  const res = fetch(`https://.../recommendations?user=${requestEvent.params.user}`);
  const recommendedProducts = (await res.json()) as Product[];
 
  return recommendedProducts;
});

在另一个 routeLoader$ 中访问 routeLoader$ 数据

您可以使用 requestEvent.resolveValue 方法在另一个 routeLoader$ 中访问一个 routeLoader$ 的数据。

src/routes/product/[productId]/index.tsx
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) => {
  // Resolve the product details from the other loader
  const product = await requestEvent.resolveValue(useProductDetails);
 
  // Use the product details to fetch personalized data
  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 的“错误”值,但还需要指示数据加载失败时,这很有用。

src/routes/product/[productId]/index.tsx
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 a failed value to indicate that product was not found
    return requestEvent.fail(404, {
      errorMessage: 'Product not found'
    });
  }
  return {
    productName: product.name
  };
});
 
export default component$(() => {
  const product = useProductDetails();
 
  if (product.value.errorMessage) {
    // Render UI for failed value
    return <div>{product.value.errorMessage}</div>;
  }
  return <div>Product name: {product.value.productName}</div>;
});

在加载器中处理相对 URL

在服务器端执行环境中,将相对 URL 转换为绝对 URL 以确保正常功能至关重要。这可以通过在相对 URL 前缀添加来自 useLocation() 函数的 origin 来实现。

创建绝对 URL
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>Relative URL: {relativeUrl}</div>
      <div>Absolute URL: {absoluteUrl}</div>
    </section>
  );
});

性能注意事项

路由加载器在服务器上执行,在每次导航后执行。这意味着它们在用户每次导航到 SPA 或 MPA 中的页面时都会执行,即使用户导航到同一个页面也是如此。

加载器在 Qwik 中间件处理程序 (onRequestonGetonPost 等) 之后执行,在 Qwik 组件渲染之前执行。这使得加载器能够尽快开始获取数据,从而减少延迟。

贡献者

感谢所有帮助改进此文档的贡献者!

  • manucorporat
  • mhevery
  • wtlin1228
  • AnthonyPAlicea
  • the-r3aper7
  • hamatoyogi
  • steve8708
  • iamyuu
  • n8sabes
  • mrhoodz
  • mjschwanitz
  • adamdbradley
  • gioboa