跳到主要内容
Learn how to build a simple SolidJS application with Deno.

使用 Deno 构建 SolidJS 应用

SolidJS 是一个声明式的 JavaScript 库,用于创建用户界面,它强调细粒度的响应式和最小的开销。当与 Deno 的现代运行时环境结合使用时,你将获得一个强大、高性能的堆栈来构建 Web 应用程序。在本教程中,我们将构建一个简单的恐龙目录应用,演示这两种技术的关键特性。

Deno 2 is backwards compatible and works with many JavaScript frameworks

Deno 向后兼容 Node 和 npm,允许你使用你偏好的 JavaScript 框架。 观看完整发布视频。

我们将介绍如何使用 Deno 构建一个简单的 SolidJS 应用

你可以直接跳到源代码,或者按照下面的步骤进行操作!

🚨️ Deno 2.1 刚刚发布,带来了第一流的 Wasm 支持长期支持改进的包管理以及更多功能。

使用 Vite 搭建 SolidJS 应用

让我们使用 Vite 来搭建我们的 SolidJS 应用程序,Vite 是一个现代构建工具,它提供了出色的开发体验,具有诸如热模块替换和优化构建等功能。

deno init --npm vite@latest solid-deno --template solid-ts

我们的后端将由 Hono 驱动,我们可以通过 JSR 安装 Hono。我们还将添加 solidjs/router 用于客户端路由和恐龙目录页面之间的导航。

deno add jsr:@hono/hono npm:@solidjs/router
了解更多关于 deno add 以及将 Deno 用作包管理器的信息。

我们还需要更新我们的 deno.json 文件,以包含一些 tasks 和 compilerOptions 来运行我们的应用

{
  "tasks": {
    "dev": "deno task dev:api & deno task dev:vite",
    "dev:api": "deno run --allow-env --allow-net --allow-read api/main.ts",
    "dev:vite": "deno run -A npm:vite",
    "build": "deno run -A npm:vite build",
    "serve": {
      "command": "deno task dev:api",
      "description": "Run the build, and then start the API server",
      "dependencies": ["deno task build"]
    }
  },
  "imports": {
    "@hono/hono": "jsr:@hono/hono@^4.6.12",
    "@solidjs/router": "npm:@solidjs/router@^0.14.10"
  },
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "solid-js",
    "lib": ["DOM", "DOM.Iterable", "ESNext"]
  }
}
从 Deno 2.1 开始,你可以将你的 tasks 编写为对象。这里我们的 serve 命令包含了一个 descriptiondependencies

太棒了!接下来,让我们设置我们的 API 后端。

设置我们的 Hono 后端

在我们的主目录中,我们将设置一个 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 服务器,api/main.ts

// api/main.ts

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

const app = new Hono();

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()
  );

  console.log(dinosaur);

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

Deno.serve(app.fetch);

这个 Hono 服务器提供了两个 API 端点

  • GET /api/dinosaurs 获取所有恐龙,以及
  • GET /api/dinosaurs/:dinosaur 通过名称获取特定恐龙

当我们运行 deno task dev 时,这个服务器将在 localhost:8000 上启动。

最后,在我们开始构建前端之前,让我们用下面的内容更新我们的 vite.config.ts 文件,特别是 server.proxy,它告知我们的 SolidJS 前端 API 端点的位置。

// vite.config.ts
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";

export default defineConfig({
  plugins: [solid()],
  server: {
    proxy: {
      "/api": {
        target: "https://127.0.0.1:8000",
        changeOrigin: true,
      },
    },
  },
});

创建我们的 SolidJS 前端

在我们开始构建前端组件之前,让我们快速在 src/types.ts 中定义 Dino 类型

// src/types.ts
export type Dino = {
  name: string;
  description: string;
};

Dino 类型接口确保了我们整个应用程序的类型安全,定义了我们恐龙数据的形状,并启用了 TypeScript 的静态类型检查。

接下来,让我们设置我们的前端来接收这些数据。我们将有两个页面

  • Index.tsx
  • Dinosaur.tsx

这是 src/pages/Index.tsx 页面的代码

// src/pages/Index.tsx

import { createSignal, For, onMount } from "solid-js";
import { A } from "@solidjs/router";
import type { Dino } from "../types.ts";

export default function Index() {
  const [dinosaurs, setDinosaurs] = createSignal<Dino[]>([]);

  onMount(async () => {
    try {
      const response = await fetch("/api/dinosaurs");
      const allDinosaurs = (await response.json()) as Dino[];
      setDinosaurs(allDinosaurs);
      console.log("Fetched dinosaurs:", allDinosaurs);
    } catch (error) {
      console.error("Failed to fetch dinosaurs:", error);
    }
  });

  return (
    <main>
      <h1>Welcome to the Dinosaur app</h1>
      <p>Click on a dinosaur below to learn more.</p>
      <For each={dinosaurs()}>
        {(dinosaur) => (
          <A href={`/${dinosaur.name.toLowerCase()}`} class="dinosaur">
            {dinosaur.name}
          </A>
        )}
      </For>
    </main>
  );
}

当使用 SolidJS 时,有一些与 React 的关键区别需要注意

  1. 我们使用 SolidJS 特定的 primitives
    • createSignal 而不是 useState
    • createEffect 而不是 useEffect
    • For 组件而不是 map
    • A 组件而不是 Link
  2. SolidJS 组件使用细粒度的响应式,所以我们将 signals 作为函数调用,例如 dinosaur() 而不是仅仅 dinosaur
  3. 路由由 @solidjs/router 而不是 react-router-dom 处理
  4. 组件导入使用 Solid 的 lazy 进行代码分割

Index 页面使用 SolidJS 的 createSignal 来管理恐龙列表,并使用 onMount 在组件加载时获取数据。我们使用 For 组件,这是 SolidJS 高效渲染列表的方式,而不是使用 JavaScript 的 map 函数。来自 @solidjs/routerA 组件创建了到各个恐龙页面的客户端导航链接,防止了完整的页面重新加载。

现在是 src/pages/Dinosaur.tsx 的单个恐龙数据页面

// src/pages/Dinosaur.tsx

import { createSignal, onMount } from "solid-js";
import { A, useParams } from "@solidjs/router";
import type { Dino } from "../types.ts";

export default function Dinosaur() {
  const params = useParams();
  const [dinosaur, setDinosaur] = createSignal<Dino>({
    name: "",
    description: "",
  });

  onMount(async () => {
    const resp = await fetch(`/api/dinosaurs/${params.selectedDinosaur}`);
    const dino = (await resp.json()) as Dino;
    setDinosaur(dino);
    console.log("Dinosaur", dino);
  });

  return (
    <div>
      <h1>{dinosaur().name}</h1>
      <p>{dinosaur().description}</p>
      <A href="/">Back to all dinosaurs</A>
    </div>
  );
}

Dinosaur 页面演示了 SolidJS 的动态路由方法,通过使用 useParams 来访问 URL 参数。它遵循与 Index 页面类似的模式,使用 createSignal 进行状态管理,使用 onMount 进行数据获取,但侧重于单个恐龙的详细信息。这个 Dinosaur 组件还展示了如何在模板中通过将 signal 值作为函数调用(例如,dinosaur().name)来访问它们,这与 React 的状态管理方式有关键区别。

最后,为了将所有内容联系起来,我们将更新 App.tsx 文件,它将作为组件服务于 IndexDinosaur 页面。App 组件使用 @solidjs/router 设置我们的路由配置,定义了两个主要路由:用于我们的恐龙列表的索引路由和用于单个恐龙页面的动态路由。路由路径中的 :selectedDinosaur 参数创建了一个动态段,它匹配 URL 中的任何恐龙名称。

// src/App.tsx

import { Route, Router } from "@solidjs/router";
import Index from "./pages/Index.tsx";
import Dinosaur from "./pages/Dinosaur.tsx";
import "./App.css";

const App = () => {
  return (
    <Router>
      <Route path="/" component={Index} />
      <Route path="/:selectedDinosaur" component={Dinosaur} />
    </Router>
  );
};

export default App;

最后,这个 App 组件将从我们的主 index 中调用

// src/index.tsx

import { render } from "solid-js/web";
import App from "./App.tsx";
import "./index.css";

const wrapper = document.getElementById("root");

if (!wrapper) {
  throw new Error("Wrapper div not found");
}

render(() => <App />, wrapper);

我们应用程序的入口点使用 SolidJS 的 render 函数将 App 组件挂载到 DOM 上。它包含一个安全检查,以确保根元素在尝试渲染之前存在,从而在初始化期间提供更好的错误处理。

现在,让我们运行 deno task dev 来同时启动前端和后端

后续步骤

🦕 现在你可以使用 Deno 构建和运行 SolidJS 应用了!这里有一些你可以增强你的恐龙应用程序的方法

SolidJS 独特的响应式 primitives、真实的 DOM reconciliation 和 Deno 的现代运行时的结合,为 Web 开发提供了极其高效的基础。由于没有 Virtual DOM 开销,并且只在需要的地方进行细粒度的更新,你的应用程序可以在保持干净、可读代码的同时实现最佳性能。

🚨️ Deno 2.1 刚刚发布 🚨️

以及更多功能!