API 参考

useContent()

useContent() 函数检索当前路由的最近内容信息。返回的对象包括

headings: ContentHeading[] | undefined;
menu: ContentMenu | undefined;

headings 数组包含有关 Markdown 文件的 <h1><h6> HTML 标题元素 的数据。

菜单是使用 menu.md 文件声明的上下文数据。有关文件格式和位置的更多信息,请参阅 菜单文件定义

useDocumentHead()

使用 useDocumentHead() 函数读取文档的 头部元数据

useDocumentHead() 检索一个只读的 DocumentHead 对象,其中包含

export interface DocumentHead {
  /**
   * Represents the `<title>` element of the document.
   */
  readonly title?: string;
  /**
   * Used to manually set meta tags in the head. Additionally, the `data`
   * property could be used to set arbitrary data which the `<head>` component
   * could later use to generate `<meta>` tags.
   */
  readonly meta?: readonly DocumentMeta[];
  /**
   * Used to manually append `<link>` elements to the `<head>`.
   */
  readonly links?: readonly DocumentLink[];
  /**
   * Used to manually append `<style>` elements to the `<head>`.
   */
  readonly styles?: readonly DocumentStyle[];
  /**
   * Arbitrary object containing custom data. When the document head is created from
   * markdown files, the frontmatter attributes that are not recognized as a well-known
   * meta names (such as title, description, author, etc...), are stored in this property.
   */
  readonly frontmatter?: Readonly<Record<string, any>>;
}

所有启动器都包含一个 <RouterHead> 组件,该组件负责生成文档的 <head> 元素。它使用 useDocumentHead() 函数检索当前头部元数据并渲染相应的 <meta><link><style><title> 元素。

src/components/router-head/router-head.tsx
import { component$ } from '@builder.io/qwik';
import { useDocumentHead } from '@builder.io/qwik-city';
 
/**
 * The RouterHead component is placed inside of the document `<head>` element.
 */
export const RouterHead = component$(() => {
  const head = useDocumentHead();
 
  return (
    <>
      <title>{head.title}</title>
 
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
 
      {head.meta.map((m) => (
        <meta {...m} />
      ))}
 
      {head.links.map((l) => (
        <link {...l} />
      ))}
 
      {head.styles.map((s) => (
        <style {...s.props} dangerouslySetInnerHTML={s.style} />
      ))}
    </>
  );
});

useLocation()

使用 useLocation() 函数检索当前位置的 RouteLocation 对象。

useLocation() 函数提供当前 URL 和参数。它还确定应用程序当前是否正在导航,这对于显示加载指示器很有用。

export interface RouteLocation {
  /**
   * Route params extracted from the URL.
   */
  readonly params: Record<string, string>;
  /**
   * The current URL.
   */
  readonly url: URL;
  /**
   * True if the app is currently navigating.
   */
  readonly isNavigating: boolean;
}

useLocation() 的返回值类似于 document.location,但在没有全局 location 对象的服务器上使用是安全的,并且它是响应式的,因此可以跟踪它。

路径路由参数

useLocation()路由参数 编码为参数。

假设您有

  • 文件:src/routes/sku/[skuId]/index.tsx
  • 用户导航到:https://example.com/sku/1234
  • 然后可以通过 useLocation().params.skuId 检索 skuId
src/routes/sku/[skuId]/index.tsx
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
 
export default component$(() => {
  const loc = useLocation();
 
  return (
    <>
      <h1>SKU</h1>
      {loc.isNavigating && <p>Loading...</p>}
      <p>pathname: {loc.url.pathname}</p>
      <p>skuId: {loc.params.skuId}</p>
    </>
  );
});

上面的代码将生成

<h1>SKU</h1>
<p>pathname: /sku/1234/</p>
<p>skuId: 1234</p>

useLocationroot.tsx 中不受支持

要从 root.tsx 访问当前 URL,请使用此模式

const serverDataUrl = useServerData<string>('url');
const url = new URL(serverDataUrl || 'https://unknown');

请注意,useLocation 是一个只读 API,您不应该尝试修改返回的 loc 对象的值。相反,请查看 useNavigate() API。

useNavigate()

useNavigate() 钩子返回一个函数,该函数以编程方式导航到下一页,而无需用户点击或导致页面完全重新加载。此函数可以使用字符串参数调用以“推送”新路径,或者不带参数调用以导致 SPA 刷新页面。这是 <Link> 组件内部使用的 API,用于支持 SPA 导航。

import { component$ } from '@builder.io/qwik';
import { useNavigate } from '@builder.io/qwik-city';
 
export default component$(() => {
  const nav = useNavigate();
 
  return (
    <>
      <button
        onClick$={async () => {
          // SPA navigate to /dashboard
          await nav('/dashboard');
        }}
      >
        Go to dashboard
      </button>
 
      <button
        onClick$={async() => {
          // refresh page: call without arguments
          await nav();
        }}
      >
        Refresh page
      </button>
    </>
  );
});

此组件有一个按钮,当点击时,将在不导致页面重新加载的情况下导航到 /dashboard

请注意,为了 SEO 和可访问性,最好使用 <Link> 组件而不是以编程方式使用 useNavigate() 在用户交互后导航到新页面。

返回

要以编程方式导航回上一页,请使用 useNavigate 和 useLocation 钩子。

import { component$ } from '@builder.io/qwik';
import { useNavigate, useLocation } from '@builder.io/qwik-city';
 
export const BackButton = component$(() => {
  const nav = useNavigate();
  const loc = useLocation();
 
  return (
    <button onClick$={() => loc.prevUrl ? window.history.back() : nav('/')}>
      Go Back
    </button>
  );
});
 

nav 函数中的回退确保如果上一页 URL (loc.prevUrl) 不可用,则用户将被重定向到默认路径(例如根路径 /)。这在导航历史记录可能不包含上一页 URL 的情况下很有用,例如,当用户直接访问特定页面而没有从应用程序中的另一个页面导航时。

routeLoader$()

routeLoader$() 函数用于在给定的页面/中间件或布局中声明一个新的服务器加载器。Qwik City 将执行给定路由匹配的所有已声明的加载器。Qwik 组件可以通过导入它们并在稍后调用返回的自定义钩子函数来引用加载器,以检索数据。

import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useGetTime = routeLoader$(async () => {
  return { time: new Date() }
});
export default component$(() => {
  const signal = useGetTime(); // Signal<{time: Date}>
  console.log('Date': signal.value.time);
  return (
    <div>{signal.value.time.toISOString()}</div>
  )
});

有关更多信息,请参阅 路由加载器 部分。

routeAction$()

routeAction$() 函数用于在给定的页面/中间件或布局中声明一个新的服务器操作。Qwik City 仅在用户交互(例如按钮点击或表单提交)后执行调用的操作。

有关更多信息,请参阅 服务器操作 部分。

<QwikCityProvider>

QwikCityProvider 组件在现有文档中初始化 Qwik City,为 Qwik City 工作提供必要的上下文,例如 useContent()useLocation()

此组件通常位于应用程序的根目录。在大多数启动器中,您将在 src/root.tsx 文件中找到它

src/root.tsx
export default component$(() => {
  /**
   * The root of a QwikCity site always start with the <QwikCityProvider> component,
   * immediately followed by the document's <head> and <body>.
   *
   * Don't remove the `<head>` and `<body>` elements.
   */
 
  return (
    <QwikCityProvider>
      <head>
        <meta charSet="utf-8" />
        <link rel="manifest" href="/manifest.json" />
        <RouterHead />
      </head>
      <body lang="en">
        <RouterOutlet />
        <ServiceWorkerRegister />
      </body>
    </QwikCityProvider>
  );
});

QwikCityProvider 不会渲染任何 DOM 元素,甚至不会渲染匹配的路由。它仅仅初始化 Qwik City 的核心逻辑,因此在同一个应用程序中不应该使用多次。

<QwikCityMockProvider>

QwikCityMockProvider 组件初始化一个用于测试的 Qwik City 上下文。它为 Qwik City 代码在测试中工作提供必要的上下文,例如 useContent()。反之亦然,对于 useNavigate()<Link>useLocation() 等等。建议您在测试文件中使用它。

QwikCityMockProvider 不会渲染任何 DOM 元素,这意味着它在快照中不可见。

如果您正在寻找关于如何将 vitest 集成到您的 Qwik 项目中的通用示例,请查看 vitest 集成文档

src/components/card.spec.tsx
import { createDOM } from '@builder.io/qwik/testing';
import { QwikCityMockProvider } from '@builder.io/qwik-city';
import { test, expect } from 'vitest';
 
// Component with two props. Uses <Link> internally. Omitted for brevity
import { Card } from './card';
 
const cases = [
  {text: 'qwik', link:'https://qwik.node.org.cn/docs/api'}, 
  {text: 'vitest', link: 'https://vitest.vuejs.ac.cn'}
];
 
test.each(cases)('should render card with %s %s', async ({text, link}) => {
  const { screen, render } = await createDOM();
  await render(
    <QwikCityMockProvider>
      <Card text={text} link={link} />
    </QwikCityMockProvider>,
  );
  expect(screen.innerHTML).toMatchSnapshot();
});

可以传递一个 goto 属性来定制测试期间的 navigate 行为。

src/components/button.spec.tsx
import { $ } from '@builder.io/qwik';
import { createDOM } from '@builder.io/qwik/testing';
import { test, expect, vi } from 'vitest';
 
// Component with one prop. Uses useNavigate internally. Omitted for brevity
import { Button } from '../button';
 
const goto = vi.fn(async (path, options) => {
  console.log(`Navigating to ${path} with ${options}`);
});
 
test('should render the button and navigate', async () => {
  const { screen, render, userEvent } = await createDOM();
  const goto$ = $(goto);
  await render(
    <QwikCityMockProvider goto={goto$}>
      <Button id="button" />
    </QwikCityMockProvider>,
  );
  expect(screen.innerHTML).toMatchSnapshot();
  await userEvent('#button', 'click');
  expect(goto).toHaveBeenCalled();
});

<RouterOutlet>

RouterOutlet 组件负责在给定时刻渲染匹配的路由,它在内部使用 useContent() 来渲染当前页面,以及所有嵌套的布局。

此组件通常位于 <body> 的子元素中,在大多数启动器中,您会在 src/root.tsx 文件中找到它(参考 QwikCityProvider 中的示例)。

<Form>

Form 组件是原生 <form> 元素的包装器,它旨在与 服务器操作 并肩工作。

由于此组件使用原生 <form> 元素,因此它将与任何支持和不支持 JavaScript 的浏览器一起工作。此外,它通过捕获 submit 事件并阻止默认行为来增强原生 <form> 元素,因此它将表现得像一个 SPA(单页应用程序),而不是一个完整的页面重新加载。

src/routes/login/index.tsx
import { component$ } from '@builder.io/qwik';
import { Form, routeAction$ } from '@builder.io/qwik-city';
 
// this action will be called when the form is submitted
export const useLoginAction = routeAction$((data, { cookies, redirect }) => {
  if (validate(data.username, data.password)) {
    cookies.set('auth', getAuthToken(data.username));
    throw redirect(302, '/dashboard');
  }
});
 
export default component$(() => {
  const login = useLoginAction();
 
  return (
    <Form action={login}>
      <input type="text" name="username" />
      <input type="password" name="password" />
      <button type="submit">Login</button>
    </Form>
  );
});

Link 组件的工作方式类似于 <a> 锚元素,但它不会导致整个页面重新加载,而是会以 SPA(单页导航)的方式导航。如果您需要在不丢失当前状态的情况下导航,这很有用。

请注意,Qwik 中的完整页面重新加载非常便宜。其他框架滥用 SPA 链接,因为完整页面重新加载需要 JS 来进行水合和重新执行所有内容。Qwik 并非如此。通过内部测试调查,使用 <a> 通常会导致最敏捷的交互。

在幕后,<Link> 组件使用 useNavigate() API 并阻止原生 <a> 的默认行为。

import { component$ } from '@builder.io/qwik';
import { useNavigate } from '@builder.io/qwik-city';
 
export const Link = component$<LinkProps>((props) => {
  const nav = useNavigate();
 
  return (
    <a
      preventdefault:click
      onClick$={() => {
        nav(props.href);
      }}
      {...props}
    >
      <Slot />
    </a>
  );
});

用法

import { component$ } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';
 
export default component$(() => {
  return (
    <div>
      <a href="/docs" class="my-link">
        full-page reload
      </a>
      <Link href="/docs" class="my-link">
        spa navigation
      </Link>
    </div>
  );
});

贡献者

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

  • manucorporat
  • adamdbradley
  • the-r3aper7
  • nnelgxorz
  • cunzaizhuyi
  • jakovljevic-mladen
  • barbosajlm
  • Eucer
  • eltociear
  • literalpie
  • Mhmdrza
  • ulic75
  • mhevery
  • jordanw66
  • igorbabko
  • mrhoodz
  • VinuB-Dev
  • billykwok
  • julianobrasil
  • hamatoyogi
  • srapport