deno.com
本頁內容

使用 Tanstack 和 Deno 建置應用程式

Tanstack 是一組與框架無關的資料管理工具。透過 Tanstack,開發人員可以使用 Query 有效率地管理伺服器狀態,使用 Table 建立強大的表格,使用 Router 處理複雜的路由,以及使用 Form 建置類型安全的表單。這些工具在 ReactVueSolid 和其他框架中都能無縫運作,同時保持出色的 TypeScript 支援。

在本教學中,我們將使用 Tanstack QueryTanstack Router 建置一個簡單的應用程式。此應用程式將顯示恐龍列表。當您點擊其中一隻恐龍時,會將您帶到包含更多詳細資訊的恐龍頁面。

您可以直接跳至原始碼,或按照以下步驟操作!

從後端 API 開始 跳到標題

在主目錄中,讓我們設定一個 api/ 目錄並建立我們的恐龍資料檔案 api/data.json

// api/data.json

[
  {
    "name": "Aardonyx",
    "description": "An early stage in the evolution of sauropods."
  },
  {
    "name": "Abelisaurus",
    "description": "\"Abel's lizard\" has been reconstructed from a single skull."
  },
  {
    "name": "Abrictosaurus",
    "description": "An early relative of Heterodontosaurus."
  },
  ...
]

這是我們將從中提取資料的地方。在完整的應用程式中,這些資料將來自資料庫。

⚠️️ 在本教學中,我們硬式編碼資料。但您可以連線到 各種資料庫,甚至在 Deno 中 使用像 Prisma 這樣的 ORM

其次,讓我們建立我們的 Hono 伺服器。我們先使用 deno addJSR 安裝 Hono

deno add jsr:@hono/hono

接下來,讓我們建立一個 api/main.ts 檔案,並填入以下內容。請注意,我們需要匯入 @hono/hono/cors 並定義金鑰屬性,以允許前端存取 API 路由。

// api/main.ts

import { Hono } from "@hono/hono";
import { cors } from "@hono/hono/cors";
import data from "./data.json" with { type: "json" };

const app = new Hono();

app.use(
  "/api/*",
  cors({
    origin: "https://127.0.0.1:5173",
    allowMethods: ["GET", "POST", "PUT", "DELETE"],
    allowHeaders: ["Content-Type", "Authorization"],
    exposeHeaders: ["Content-Type", "Authorization"],
    credentials: true,
    maxAge: 600,
  }),
);

app.get("/", (c) => {
  return c.text("Welcome to the dinosaur API!");
});

app.get("/api/dinosaurs", (c) => {
  return c.json(data);
});

app.get("/api/dinosaurs/:dinosaur", (c) => {
  if (!c.req.param("dinosaur")) {
    return c.text("No dinosaur name provided.");
  }

  const dinosaur = data.find((item) =>
    item.name.toLowerCase() === c.req.param("dinosaur").toLowerCase()
  );

  if (dinosaur) {
    return c.json(dinosaur);
  } else {
    return c.notFound();
  }
});

Deno.serve(app.fetch);

Hono 伺服器提供兩個 API 端點

  • GET /api/dinosaurs 取得所有恐龍,以及
  • GET /api/dinosaurs/:dinosaur 依名稱取得特定恐龍

在開始處理前端之前,讓我們更新 deno.json 檔案中的 deno tasks。您的檔案應該看起來像這樣

{
  "tasks": {
    "dev": "deno --allow-env --allow-net api/main.ts"
  }
  // ...
}

現在,當我們執行 deno task dev 時,後端伺服器將在 localhost:8000 上啟動。

建立 Tanstack 驅動的前端 跳到標題

讓我們建立將使用這些資料的前端。首先,我們將使用 TypeScript 範本在目前目錄中快速建立一個新的 React 應用程式 Vite 專案

deno init --npm vite@latest --template react-ts ./

然後,我們將安裝 Tanstack 特定的相依性

deno install npm:@tanstack/react-query npm:@tanstack/react-router

讓我們更新 deno.json 檔案中的 deno tasks,以新增啟動 Vite 伺服器的命令

// deno.json
{
  "tasks": {
    "dev": "deno task dev:api & deno task dev:vite",
    "dev:api": "deno --allow-env --allow-net api/main.ts",
    "dev:vite": "deno -A npm:vite"
  }
  // ...
}

我們可以繼續建置我們的元件。我們的應用程式將需要兩個主要頁面

  • DinosaurList.tsx:索引頁面,將列出所有恐龍,以及
  • Dinosaur.tsx:子頁面,顯示有關單一恐龍的資訊

讓我們建立一個新的 ./src/components 目錄,並在其中建立檔案 DinosaurList.tsx

// ./src/components/DinosaurList.tsx

import { useQuery } from "@tanstack/react-query";
import { Link } from "@tanstack/react-router";

async function fetchDinosaurs() {
  const response = await fetch("https://127.0.0.1:8000/api/dinosaurs/");
  if (!response.ok) {
    throw new Error("Failed to fetch dinosaurs");
  }
  return response.json();
}

export function DinosaurList() {
  const {
    data: dinosaurs,
    isLoading,
    error,
  } = useQuery({
    queryKey: ["dinosaurs"],
    queryFn: fetchDinosaurs,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error instanceof Error) {
    return <div>An error occurred: {error.message}</div>;
  }

  return (
    <div>
      <h2 className="text-xl font-semibold mb-4">Dinosaur List</h2>
      <ul className="space-y-2">
        {dinosaurs?.map((dino: { name: string; description: string }) => (
          <li key={dino.name}>
            <Link
              to="/dinosaur/$name"
              params={{ name: dino.name }}
              className="text-blue-500 hover:underline"
            >
              {dino.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

這使用來自 Tanstack QueryuseQuery 自動提取和快取恐龍資料,並內建載入和錯誤狀態。然後它使用來自 Tanstack RouterLink 建立具有類型安全路由參數的客戶端導航連結。

接下來,讓我們在 ./src/components/ 資料夾中建立 DinosaurDetail.tsx 元件,它將顯示有關單一恐龍的詳細資訊

// ./src/components/DinosaurDetail.tsx

import { useParams } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query";

async function fetchDinosaurDetail(name: string) {
  const response = await fetch(`https://127.0.0.1:8000/api/dinosaurs/${name}`);
  if (!response.ok) {
    throw new Error("Failed to fetch dinosaur detail");
  }
  return response.json();
}

export function DinosaurDetail() {
  const { name } = useParams({ from: "/dinosaur/$name" });
  const {
    data: dinosaur,
    isLoading,
    error,
  } = useQuery({
    queryKey: ["dinosaur", name],
    queryFn: () => fetchDinosaurDetail(name),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error instanceof Error) {
    return <div>An error occurred: {error.message}</div>;
  }

  return (
    <div>
      <h2 className="text-xl font-semibold mb-4">{name}</h2>
      <p>{dinosaur?.description}</p>
    </div>
  );
}

同樣地,這使用來自 Tanstack QueryuseQuery 來提取和快取個別恐龍詳細資訊,並使用 queryKey 包含恐龍名稱以確保正確快取。此外,我們使用來自 Tanstack RouteruseParams 安全地提取和類型化在我們的路由設定中定義的 URL 參數。

在我們可以執行此程式之前,我們需要將這些元件封裝到版面配置中。讓我們在 ./src/components/ 資料夾中建立另一個名為 Layout.tsx 的檔案

// ./src/components/Layout.tsx

export function Layout() {
  return (
    <div className="p-4">
      <h1 className="text-2xl font-bold mb-4">Dinosaur Encyclopedia</h1>
      <nav className="mb-4">
        <Link to="/" className="text-blue-500 hover:underline">
          Home
        </Link>
      </nav>
      <Outlet />
    </div>
  );
}

您可能會注意到在我們新建立的版面配置底部附近的 Outlet 元件。此元件來自 Tanstack Router,並呈現子路由的內容,允許巢狀路由,同時保持一致的版面配置結構。

接下來,我們必須將此版面配置與 ./src/main.tsx 連結起來,這是一個重要的檔案,用於設定 Tanstack Query 客戶端以管理伺服器狀態,以及 Tanstack Router 以處理導航

// ./src/main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createRouter, RouterProvider } from "@tanstack/react-router";
import { routeTree } from "./routeTree";

const queryClient = new QueryClient();

const router = createRouter({ routeTree });

declare module "@tanstack/react-router" {
  interface Register {
    router: typeof router;
  }
}

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  </React.StrictMode>,
);

您會注意到我們匯入了 QueryClientProvider,它包裝了整個應用程式,以允許查詢快取和狀態管理。我們也匯入了 RouterProvider,它將我們定義的路由連接到 React 的呈現系統。

最後,我們需要在 ./src/ 目錄中定義一個 routeTree.tsx 檔案。此檔案使用 Tanstack Router 的類型安全路由定義來定義我們的應用程式路由結構

// ./src/routeTree.tsx

import { RootRoute, Route } from "@tanstack/react-router";
import { DinosaurList } from "./components/DinosaurList";
import { DinosaurDetail } from "./components/DinosaurDetail";
import { Layout } from "./components/Layout";

const rootRoute = new RootRoute({
  component: Layout,
});

const indexRoute = new Route({
  getParentRoute: () => rootRoute,
  path: "/",
  component: DinosaurList,
});

const dinosaurRoute = new Route({
  getParentRoute: () => rootRoute,
  path: "dinosaur/$name",
  component: DinosaurDetail,
});

export const routeTree = rootRoute.addChildren([indexRoute, dinosaurRoute]);

./src/routeTree.tsx 中,我們建立一個以 Layout 作為根元件的路由層次結構。然後我們設定兩個子路由、它們的路徑和元件 — 一個用於恐龍列表 DinosaurList,另一個用於具有動態參數的個別恐龍詳細資訊 DinosaurDetail

完成所有這些之後,我們可以執行此專案

deno task dev

下一步 跳到標題

這僅僅是使用 Deno 和 Tanstack 建置的開始。您可以新增持久性資料儲存,例如 使用像 Postgres 或 MongoDB 這樣的資料庫 和像 DrizzlePrisma 這樣的 ORM。或者將您的應用程式部署到 AWSDigital OceanGoogle Cloud Run

您也可以使用 Tanstack Query 的重新提取功能 新增即時更新、實作無限捲動 以用於大型恐龍列表,或使用 Tanstack Table 新增複雜的篩選和排序。Deno 的內建 Web 標準、工具和原生 TypeScript 支援,以及 Tanstack 強大的資料管理功能,為建置穩健的 Web 應用程式開啟了無數的可能性。

您找到需要的資訊了嗎?

隱私權政策