引言
如果你已经熟练掌握 Rust 和原生 Web 开发(HTML、CSS、JavaScript),那么你可能已经对前端开发的种种痛点深有体会:JavaScript 的动态类型导致运行时错误、状态管理容易失控、DOM 操作繁琐且容易引发性能问题、缺乏类型安全的组件化……而 Rust 以其安全性、高性能和卓越的工具链,为前端开发带来了新的可能性。
Dioxus 是一个用于构建跨平台用户界面的 Rust 框架,它借鉴了 React 的思想,但充分利用了 Rust 的语言特性。它可以将你的 Rust 代码编译为 WebAssembly,在浏览器中运行,同时也支持桌面、移动和服务器端渲染。本教程将从你已经熟悉的原生 Web 视角出发,带你快速掌握 Dioxus 的核心概念和用法。
环境搭建
原生 Web
- 只需要一个文本编辑器和一个浏览器。
- 可选:Node.js 和 npm 用于构建工具(如 webpack)。
Dioxus
- Rust 工具链:通过 rustup 安装 Rust,确保
wasm32-unknown-unknown目标已添加。rustup target add wasm32-unknown-unknown - Dioxus CLI:方便创建、构建和运行项目。
cargo install dioxus-cli - 可选:如果你需要与 JavaScript 生态交互,可能需要
wasm-bindgen等工具。
对比:Dioxus 的工具链完全基于 Rust,无需 Node.js 环境(除非你需要与 npm 包交互)。CLI 提供了类似 create-react-app 的体验,但更轻量。
第一个应用:Hello World
原生 Web
创建一个 index.html:
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<div id="app"></div>
<script>
const app = document.getElementById('app');
app.innerHTML = '<h1>Hello, World!</h1>';
</script>
</body>
</html>
使用 Dioxus CLI 创建项目
当你运行 dx new hello-dioxus 时,CLI 会通过一系列交互式问题引导你完成项目初始化。下面详细解释每个选项,以便你根据需求做出选择。
模板选择
? 🤷 Which sub-template should be expanded? ›
❯ Bare-Bones
Jumpstart
Workspace
- Bare-Bones:生成最简项目结构,只包含核心依赖和基础文件。适合完全自定义配置的开发者。
- Jumpstart:包含一些常用预设,例如可能集成了路由器、CSS 处理等,让你快速开始一个功能更完整的应用。
- Workspace:创建一个 Cargo 工作区,适合需要管理多个相关包(例如将 UI 和逻辑分离)的项目。
选择不同模板会影响生成的文件和 Cargo.toml 中的依赖。对于初学者,通常建议选择 Bare-Bones 或 Jumpstart,后者可以帮你省去一些初始配置。
功能选项
选择模板后,CLI 会继续询问一系列功能选项:
✔ 🤷 Do you want to use Dioxus Fullstack? · false
✔ 🤷 Do you want to use Dioxus Router? · false
✔ 🤷 Do you want to use Tailwind CSS? · false
✔ 🤷 Do you want to include prompts for LLMs? · false
- Dioxus Fullstack:是否启用全栈功能?启用后,项目会添加
dioxus-fullstack依赖,允许你在同一应用中编写服务器端代码(如服务器函数),并支持服务端渲染(SSR)。如果你计划构建需要与后端紧密集成的应用,可以选择true。 - Dioxus Router:是否包含官方路由库
dioxus-router?路由是现代单页应用(SPA)的必备功能,如果你的应用有多个页面,建议启用。 - Tailwind CSS:是否集成 Tailwind CSS 工具链?如果选择
true,CLI 会生成tailwind.config.js和相应的 CSS 文件,并配置构建过程。Tailwind 是一个流行的原子化 CSS 框架,可以大幅提高样式开发效率。 - Prompts for LLMs:是否包含用于大型语言模型(如 AI 代码生成)的提示文件(如
AGENTS.md)?这些文件为 AI 助手提供项目上下文,如果你打算使用 Cursor、Copilot 等工具辅助开发,可以选择true。
默认平台
✔ 🤷 Which platform do you want DX to serve by default? · Web
- Web:默认目标为浏览器,生成 WebAssembly 应用。
- Desktop:默认目标为桌面应用,使用系统 WebView 或原生渲染(需要
dioxus-desktop)。 - Mobile:默认目标为移动应用(实验性)。
选择后,dx serve 命令将针对该平台启动开发服务器。
项目生成示例
假设我们选择了 Bare-Bones 模板,并禁用了所有功能选项,最终生成的目录结构大致如下:
hello-dioxus/
├── .gitignore
├── AGENTS.md # 如果启用了 LLM prompts 则生成
├── Cargo.toml # 项目依赖和配置
├── Dioxus.toml # Dioxus 项目配置文件(如开发服务器端口、资源路径等)
├── README.md
├── assets/ # 静态资源文件夹
│ ├── favicon.ico
│ ├── header.svg
│ ├── main.css
│ └── tailwind.css # 如果启用了 Tailwind 则生成
├── clippy.toml # Clippy lint 配置
└── src/
└── main.rs # 应用入口
Cargo.toml 中会自动添加必要的依赖(如 dioxus、dioxus-web 等)。src/main.rs 包含一个简单的 Hello World 组件。
现在,进入项目目录并运行开发服务器:
cd hello-dioxus
dx serve
默认情况下,应用将在 http://localhost:8080 打开。
代码解析
打开 src/main.rs,你会看到:
use dioxus::prelude::*;
fn main() {
dioxus_web::launch(App);
}
#[component]
fn App() -> Element {
rsx! {
h1 { "Hello, World!" }
}
}
对比原生 Web:
- 我们不再手动操作 DOM(如
document.getElementById),而是通过rsx!宏声明 UI。 - 应用通过
dioxus_web::launch挂载到 DOM(默认挂载到body或指定的元素)。 - 所有 Rust 代码最终编译为 WASM,在浏览器中运行。
下一步
现在你已经成功创建并运行了第一个 Dioxus 应用,接下来我们将深入核心概念,看看 Dioxus 如何以声明式、类型安全的方式构建交互界面。
核心概念
(以下内容与之前相同,但我们可以保持原样,因为用户没有指出问题)
1. 组件化
原生 Web:可以使用原生 Web Components,但需要编写较多样板代码,且与框架的响应式系统集成不自然。或者手动创建元素、添加事件监听,容易导致代码混乱。
Dioxus:组件是返回 Element 的 Rust 函数,可以通过 #[component] 属性标记,并接受 Props。
#[derive(Props, PartialEq, Clone)]
struct WelcomeProps {
name: String,
}
#[component]
fn Welcome(props: WelcomeProps) -> Element {
rsx! {
div { "Hello, ", props.name }
}
}
// 使用
rsx! { Welcome { name: "Alice" } }
对比:
- Props 是类型安全的:你必须传递正确类型和名称的属性,否则编译失败。
- 组件默认是纯函数,依赖 props 和内部状态。
- 没有
this绑定问题,一切都是 Rust 的所有权和借用规则。
2. 状态管理
原生 Web:需要手动维护状态,并在变化时更新 DOM。例如:
let count = 0;
const button = document.querySelector('button');
button.addEventListener('click', () => {
count++;
button.textContent = `Count: ${count}`;
});
随着状态增多,更新逻辑分散,容易出错。
Dioxus:使用 Hooks 管理状态,最常见的是 use_state。
#[component]
fn Counter() -> Element {
let mut count = use_state(|| 0);
rsx! {
button {
onclick: move |_| count += 1,
"Count: {count}"
}
}
}
对比:
use_state返回一个智能指针(类似Rc),通过Deref实现读取,通过set方法或+=运算符(实现了std::ops::AddAssign)更新。- 当状态变化时,Dioxus 自动重新运行组件函数,生成新的虚拟 DOM,并高效地更新真实 DOM。
- 避免了手动 DOM 操作,状态与 UI 自动同步。
3. 事件处理
原生 Web:使用 addEventListener 或 onclick 属性。回调函数中操作 DOM 或状态。
Dioxus:在 rsx! 中直接添加事件监听器,闭包会捕获环境中的状态。
rsx! {
button {
onclick: move |event| {
println!("Clicked at: {:?}", event.client_coordinates());
count += 1;
},
"Click me"
}
}
对比:
- 事件类型是强类型的(如
MouseEvent),可以访问坐标、按键等。 - 闭包是
'static的,需要注意移动捕获(使用move关键字)以避免生命周期问题。 - 事件处理函数中可以直接修改状态(通过
set或+=),触发重新渲染。
4. 生命周期与副作用
原生 Web:使用 window.addEventListener('load', ...) 或 DOMContentLoaded 来执行初始化;使用 setInterval 等需要手动清理,容易导致内存泄漏。
Dioxus:use_effect Hook 用于处理副作用,可以指定依赖项,并在组件卸载时自动清理。
use_effect(|| {
// 副作用代码,例如订阅事件
let interval = set_interval(|| {
// do something
}, 1000);
// 返回一个清理函数(可选)
move || {
clear_interval(interval);
}
}, deps); // 依赖项数组,当依赖变化时重新运行
对比:
- 依赖项明确控制副作用的执行时机,避免不必要的重复执行。
- 清理函数在组件卸载或副作用重新运行前调用,防止内存泄漏。
5. 列表和条件渲染
原生 Web:需要手动创建、插入、删除 DOM 节点。例如:
const list = document.getElementById('list');
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
list.appendChild(li);
});
条件渲染则需要 if 语句和 removeChild。
Dioxus:在 rsx! 中可以使用 Rust 的迭代器和条件表达式。
#[component]
fn ItemList(items: Vec<String>) -> Element {
rsx! {
ul {
{items.iter().map(|item| rsx! { li { "{item}" } })}
}
if items.is_empty() {
p { "No items" }
}
}
}
对比:
- 直接在
rsx!中使用 Rust 表达式生成元素,无需手动 DOM 操作。 - Dioxus 的 diffing 算法会自动更新列表(通过
key属性优化)。 - 条件渲染就是 Rust 的
if表达式,非常自然。
6. 样式
原生 Web:使用 CSS 文件、内联样式或 CSS-in-JS 库。
Dioxus:支持多种方式:
- 内联样式:通过
style属性传入字符串或键值对。div { style: "color: red; background-color: black;", "Styled" } - CSS 类:通过
class属性。div { class: "container", "Content" } - CSS-in-Rust:可以使用
dioxus-free-css等库在 Rust 中编写样式,编译为 CSS。 - 全局 CSS:在
index.html中引入传统 CSS 文件(通过assets/文件夹)。
对比:
- 样式隔离:组件默认不会互相影响,除非使用全局类。
- 类型安全:可以通过类型定义限制类名,避免拼写错误(配合
dioxus-free-css等库)。
7. 路由
原生 Web:使用 History API 手动管理 URL,监听 popstate 事件,渲染相应内容。
Dioxus:提供 dioxus-router 库,声明式路由。
use dioxus_router::prelude::*;
#[derive(Routable, Clone)]
enum Route {
#[route("/")]
Home {},
#[route("/about")]
About {},
#[route("/users/:id")]
User { id: String },
}
#[component]
fn App() -> Element {
rsx! {
Router::<Route> {}
}
}
然后在每个路由对应的组件中渲染内容。
对比:
- 路由类型安全:路由枚举确保你只能访问定义的路径。
- 嵌套路由、链接、重定向等常见功能开箱即用。
- 与组件集成自然,无需手动监听 URL。
8. 与 JavaScript 互操作
原生 Web:JavaScript 直接调用 Web API。
Dioxus:通过 web-sys 和 js-sys crate 调用浏览器 API。这些 crate 提供了对 Web API 的 Rust 绑定,类型安全。
use web_sys::window;
let window = window().unwrap();
window.alert_with_message("Hello from Rust!").unwrap();
如果需要调用自定义 JavaScript 函数,可以使用 wasm-bindgen 的 #[wasm_bindgen] 属性。
对比:
- 几乎所有 Web API 都有 Rust 绑定,可以安全调用。
- 如果需要使用 npm 包,可以通过
wasm-bindgen导入,但需要额外配置(如wasm-pack或trunk)。
高级特性
服务器函数(全栈)
Dioxus 支持服务器端渲染(SSR)和全栈开发。你可以定义服务器函数,在客户端调用,自动处理序列化和网络请求。
#[server(GetServerData)]
async fn get_server_data() -> Result<String, ServerFnError> {
// 服务器端代码
Ok("Data from server".to_string())
}
#[component]
fn App() -> Element {
let data = use_server_future(get_server_data())?;
rsx! {
div { "{data}" }
}
}
对比原生:原生需要手动编写 API 路由、fetch 请求、处理 JSON 序列化/反序列化。Dioxus 服务器函数将这一切抽象化,类型安全且简洁。
跨平台开发
Dioxus 不仅支持 Web,还支持桌面(通过 dioxus-desktop,基于 Tauri)和移动(通过 dioxus-mobile)。代码可以共享,只需替换渲染器。
// 桌面应用
fn main() {
dioxus_desktop::launch(App);
}
对比原生:原生 Web 只运行在浏览器中;Dioxus 允许你用同一套代码构建桌面和移动应用,类似于 Electron 但更轻量(使用系统 webview 或原生渲染)。
构建和部署
原生 Web
通常使用 webpack、vite 等工具打包,部署静态文件到服务器或 CDN。
Dioxus
使用 Dioxus CLI 构建:
dx build --release
输出目录为 dist,包含 WASM 文件、JS 胶水代码和 HTML。你可以将这些文件部署到任何静态文件服务器。
对比:
- Dioxus 构建产物体积相对较小(WASM 二进制可能比 JS 包大,但可以通过优化减小)。
- 无需配置复杂的构建工具(如 webpack),CLI 已封装好。
总结
Dioxus 将 Rust 的安全、性能和类型系统带入前端开发,同时借鉴了 React 的声明式编程模型。与原生 Web 相比,Dioxus 提供了:
- 类型安全:组件、状态、事件、路由等都在编译时检查。
- 声明式 UI:用
rsx!描述界面,状态自动驱动更新。 - 自动 DOM 更新:虚拟 DOM diffing 算法优化性能。
- 跨平台:同一套代码可运行在 Web、桌面、移动。
- 全栈能力:服务器函数简化前后端通信。
对于已经熟悉 Rust 和原生 Web 的开发者,Dioxus 的学习曲线主要在于理解其 Hooks 系统和响应式模型,而这些与 React 非常相似。相信通过本教程的对比,你已经能够快速上手并开始构建自己的 Dioxus 应用。