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."
  },
  ...
]

这是我们的数据将从中提取的地方。在一个完整的应用程序中,这些数据将来自数据库。

⚠️️ 在本教程中,我们硬编码了数据。但是您可以连接到各种数据库,甚至可以使用 像 Prisma 这样的 ORM 与 Deno。

其次,让我们创建我们的 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 模板,使用 Vite 快速搭建一个新的 React 应用

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 应用程序开辟了无数可能性。

您找到所需的内容了吗?

隐私政策