使用 Tanstack 和 Deno 建置應用程式
Tanstack 是一組與框架無關的資料管理工具。透過 Tanstack,開發人員可以使用 Query 有效率地管理伺服器狀態,使用 Table 建立強大的表格,使用 Router 處理複雜的路由,以及使用 Form 建置類型安全的表單。這些工具在 React、Vue、Solid 和其他框架中都能無縫運作,同時保持出色的 TypeScript 支援。
在本教學中,我們將使用 Tanstack Query 和 Tanstack 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 add
從 JSR 安裝 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 Query 的 useQuery
自動提取和快取恐龍資料,並內建載入和錯誤狀態。然後它使用來自 Tanstack Router 的 Link
建立具有類型安全路由參數的客戶端導航連結。
接下來,讓我們在 ./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 Query 的 useQuery
來提取和快取個別恐龍詳細資訊,並使用 queryKey
包含恐龍名稱以確保正確快取。此外,我們使用來自 Tanstack Router 的 useParams
安全地提取和類型化在我們的路由設定中定義的 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 這樣的資料庫 和像 Drizzle 或 Prisma 這樣的 ORM。或者將您的應用程式部署到 AWS、Digital Ocean 或 Google Cloud Run
您也可以使用 Tanstack Query 的重新提取功能 新增即時更新、實作無限捲動 以用於大型恐龍列表,或使用 Tanstack Table 新增複雜的篩選和排序。Deno 的內建 Web 標準、工具和原生 TypeScript 支援,以及 Tanstack 強大的資料管理功能,為建置穩健的 Web 應用程式開啟了無數的可能性。