API 參考

useContent()

useContent() 函數會擷取目前路由最接近的內容資訊。傳回的物件包含

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

headings 陣列包含 Markdown 檔案中,從 <h1><h6>HTML 標題元素 的資料。

選單是使用 menu.md 檔案宣告的上下文資料。如需有關檔案格式和位置的更多資訊,請參閱選單檔案定義

useDocumentHead()

使用 useDocumentHead() 函式來讀取文件 head 中繼資料

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() 函式來擷取當前的 head 中繼資料,並渲染適當的 <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>

不支援在 root.tsx 中使用 useLocation

要從 root.tsx 存取當前的 URL,請使用以下模式

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

請注意,useLocation 是一個唯讀 API,您永遠不應該嘗試變更回傳的 loc 物件的值。請改為查看 useNavigate() API。

useNavigate()

useNavigate() 鉤子會回傳一個函式,該函式會以程式化的方式導航到下一頁,而不需要使用者點擊或導致整個頁面重新載入。這個函式可以使用字串參數來「推送」新的路徑,或者不使用參數來導致頁面的 SPA 重新整理。這是 <Link> 元件內部用於支援 SPA 導航的 API。

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.dev.org.tw/docs/api'}, 
  {text: 'vitest', link: 'https://vitest.dev.org.tw'}
];
 
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