使用 Deno 构建 SolidJS 应用程序
SolidJS 是一个声明式 JavaScript 库,用于创建强调细粒度响应性和最小开销的用户界面。与 Deno 的现代运行时环境相结合,您将获得一个强大、高性能的 Web 应用程序构建堆栈。在本教程中,我们将构建一个简单的恐龙目录应用程序,以展示这两种技术的关键特性。
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 安装它。我们还会添加 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."
},
...
]
这里是我们数据提取的来源。在一个完整的应用程序中,这些数据将来自数据库。
⚠️️ 在本教程中,我们硬编码数据。但您可以连接到各种数据库,甚至可以使用 Deno 的 Prisma 等 ORM。
其次,我们需要我们的 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://: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
组件以实现更高效的列表渲染
SolidJS 独特的响应式原语、真正的 DOM 协调以及 Deno 的现代运行时相结合,为 Web 开发提供了极其高效的基础。没有虚拟 DOM 开销,并且只在需要时进行细粒度更新,您的应用程序可以在保持代码整洁、可读的同时实现最佳性能。
🚨️ Deno 2.1 刚刚发布 🚨️
以及更多!