使用 Deno 构建 SolidJS 应用
SolidJS 是一个声明式的 JavaScript 库,用于创建用户界面,它强调细粒度的响应性和最小的开销。当与 Deno 的现代运行时环境结合使用时,您将获得一个强大、高性能的堆栈,用于构建 Web 应用程序。在本教程中,我们将构建一个简单的恐龙目录应用程序,演示这两种技术的关键特性。
我们将介绍如何使用 Deno 构建一个简单的 SolidJS 应用
随时直接跳到源代码或继续阅读以下内容!
使用 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
文件,以包含一些任务和 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"]
}
}
tasks
编写为对象。这里我们的 serve
命令包含了一个 description
和 dependencies
。太棒了!接下来,让我们设置我们的 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 的不同之处需要注意
- 我们使用 SolidJS 特定的原语
createSignal
代替useState
createEffect
代替useEffect
For
组件代替map
A
组件代替Link
- SolidJS 组件使用细粒度的响应性,所以我们将信号作为函数调用,例如
dinosaur()
而不仅仅是dinosaur
- 路由由
@solidjs/router
而不是react-router-dom
处理 - 组件导入使用 Solid 的
lazy
进行代码分割
Index
页面使用 SolidJS 的 createSignal
来管理恐龙列表,并使用 onMount
在组件加载时获取数据。我们使用 For
组件,这是 SolidJS 渲染列表的有效方式,而不是使用 JavaScript 的 map 函数。来自 @solidjs/router
的 A
组件创建了到各个恐龙页面的客户端导航链接,防止了整个页面重新加载。
现在是 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
页面通过使用 useParams
访问 URL 参数,演示了 SolidJS 的动态路由方法。它遵循与 Index
页面类似的模式,使用 createSignal
进行状态管理,使用 onMount
进行数据获取,但侧重于单个恐龙的详细信息。此 Dinosaur
组件还展示了如何在模板中通过将信号作为函数调用(例如,dinosaur().name
)来访问信号值,这与 React 的状态管理是一个关键区别。
最后,为了将所有内容联系起来,我们将更新 App.tsx
文件,它将作为组件服务 Index
和 Dinosaur
页面。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
组件将从我们的主索引中调用
// 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 应用了!以下是一些你可以增强你的恐龙应用程序的方法:
- 使用像 Postgres 或 MongoDB 这样的数据库和像 Drizzle 或 Prisma 这样的 ORM 添加持久数据存储
- 使用 SolidJS 的
createContext
实现全局状态,以便在组件之间共享数据 - 使用
createResource
的 loading 属性添加加载状态 - 使用
lazy
导入实现基于路由的代码分割 - 使用
Index
组件进行更高效的列表渲染 - 将你的应用程序部署到 AWS、Digital Ocean 或 Google Cloud Run
SolidJS 独特的响应式原语、真正的 DOM 协调和 Deno 的现代运行时环境的结合为 Web 开发提供了非常高效的基础。由于没有 Virtual DOM 开销,并且仅在需要时进行细粒度更新,您的应用程序可以在保持清晰、可读代码的同时实现最佳性能。