Rust跨平台UI框架Dioxus的使用:构建高性能Web、桌面和移动应用的React式组件库
Dioxus是一个用于在Rust中构建跨平台应用程序的框架。通过一个代码库,您可以使用全栈服务器功能构建Web、桌面和移动应用程序。Dioxus的设计旨在让熟悉HTML、CSS和JavaScript等Web技术的开发人员易于学习。
概览
Dioxus是一个跨平台应用程序框架,使开发人员能够使用Rust构建美观、快速、类型安全的应用程序。默认情况下,Dioxus应用程序使用HTML和CSS声明。Dioxus包含许多有用的功能:
- RSX标记和资源的热重载
- 具有日志记录、项目模板、linting等的交互式CLI
- 用于部署到Web、macOS、Linux和Windows的集成捆绑器
- 支持现代Web功能,如SSR、Hydration和HTML流
- 通过JNI(Android)、CoreFoundation(Apple)和web-sys(Web)直接访问系统API
- 类型安全的应用程序路由和服务器功能
快速开始
要开始使用Dioxus,您需要获取dioxus-cli工具:dx
。我们使用cargo-binstall
分发dx
- 如果您已经安装了binstall,请跳过此步骤。
# 如果您已经安装了cargo-binstall,请跳过
cargo install cargo-binstall
# 安装预编译的`dx`工具
cargo binstall dioxus-cli
# 创建一个新应用程序,遵循模板
dx new my-app && cd my-app
# 然后启动!
dx serve --platform desktop
您的第一个应用程序
所有Dioxus应用程序都是通过组合返回Element
的函数来构建的。
要启动应用程序,我们使用launch
方法。在启动函数中,我们传递应用程序的根Component
。
use dioxus::prelude::*;
fn main() {
dioxus::launch(App);
}
// #[component]属性简化了组件创建。
// 它不是必需的,但强烈推荐。它会检查不正确的组件定义并帮助您创建props结构体。
#[component]
fn App() -> Element {
rsx! { "hello world!" }
}
元素和您的第一个组件
您可以使用rsx!
宏以类似jsx的语法创建元素。
rsx!
中的任何元素都可以有属性、监听器和子元素。为了
一致性,我们强制所有属性和监听器在子元素之前列出。
# use dioxus::prelude::*;
let value = "123";
rsx! {
div {
class: "my-class {value}", // <--- 属性
onclick: move |_| println!("clicked!"), // <--- 监听器
h1 { "hello world" } // <--- 子元素
}
};
rsx!
宏接受"结构体形式"的属性。任何包含在花括号中并实现IntoDynNode
的Rust表达式都将被解析为子元素。我们有两个例外:for
循环和if
语句都被解析,它们的体被解析为rsx节点。
# use dioxus::prelude::*;
rsx! {
div {
for _ in 0..10 {
span { "hello world" }
}
}
};
将所有内容放在一起,我们可以编写一个简单的组件来渲染元素列表:
# use dioxus::prelude::*;
#[component]
fn App() -> Element {
let name = "dave";
rsx! {
h1 { "Hello, {name}!" }
div { class: "my-class", id: "my-id",
for i in 0..5 {
div { "FizzBuzz: {i}" }
}
}
}
}
组件
我们可以组合这些函数组件来构建复杂的应用程序。每个新组件都接受一些属性。对于没有显式属性的组件,我们可以完全省略类型。
在Dioxus中,所有属性默认都使用Clone
和PartialEq
进行记忆化。对于无法克隆的props,只需将字段包装在ReadOnlySignal
中,Dioxus将为您处理类型转换。
# use dioxus::prelude::*;
# #[component] fn Header(title: String, color: String) -> Element { todo!() }
#[component]
fn App() -> Element {
rsx! {
Header {
title: "My App",
color: "red",
}
}
}
#[component]
宏将帮助我们自动为组件创建props结构体:
# use dioxus::prelude::*;
// 组件宏将我们函数的参数转换为可以在rsx中传递给组件的命名字段
#[component]
fn Header(title: String, color: String) -> Element {
rsx! {
div {
background_color: "{color}",
h1 { "{title}" }
}
}
}
Hooks
虽然组件是UI元素的可重用形式,但hooks是逻辑的可重用形式。Hooks提供了一种从Dioxus内部的Scope
检索状态并使用它来渲染UI元素的方法。
按照约定,所有hooks都是以use_
开头的函数。我们可以使用hooks来定义状态并从监听器内部修改它。
# use dioxus::prelude::*;
#[component]
fn App() -> Element {
// use signal hook在组件创建时运行一次,然后在第一次运行后返回当前值
let name = use_signal(|| "world");
rsx! { "hello {name}!" }
}
Hooks对它们的使用方式很敏感。要使用hooks,您必须遵守"hooks规则":
- Hooks只能在组件或另一个hook的主体中调用。不能在另一个表达式(如循环、条件或函数调用)内部调用。
- Hooks应以"use_"开头
Hooks让我们可以向组件添加状态字段,而无需声明显式状态结构体。然而,这意味着我们需要以正确的顺序"加载"结构体。如果顺序错误,hook将选择错误的状态并panic。
Dioxus包含许多内置hooks,您可以在组件中使用。如果这些hooks不符合您的用例,您还可以使用自定义hooks扩展Dioxus。
将所有内容放在一起
使用组件、rsx和hooks,我们可以构建一个简单的应用程序。
use dioxus::prelude::*;
fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
let mut count = use_signal(|| 0);
rsx! {
div { "Count: {count}" }
button { onclick: move |_| count += 1, "Increment" }
button { onclick: move |_| count -= 1, "Decrement" }
}
}
结论
此概述并未涵盖所有内容。请务必查看官方网站上的教程和指南以获取更多详细信息。
除了此概述之外,Dioxus还支持:
- 服务器端渲染
- 并发渲染(支持异步)
- Web/桌面/移动支持
- 预渲染和水合
- 片段和Suspense
- 内联样式
- 自定义事件处理程序
- 自定义元素
- 基本细粒度响应性(如SolidJS/Svelte)
- 以及更多!
构建酷炫的东西 ✌️
完整示例代码
use dioxus::prelude::*;
fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
let mut count = use_signal(|| 0);
rsx! {
div {
h1 { "Dioxus Counter App" }
p { "Current count: {count}" }
button {
onclick: move |_| count += 1,
"Increment"
}
button {
onclick: move |_| count -= 1,
"Decrement"
}
button {
onclick: move |_| count.set(0),
"Reset"
}
}
}
}
Dioxus:跨平台UI框架的使用指南
框架介绍
Dioxus是一个基于Rust语言构建的声明式、React式UI框架,支持跨平台开发。它允许开发者使用相同的代码库构建高性能的Web应用、桌面应用和移动应用。Dioxus采用虚拟DOM技术,提供类似React的开发体验,同时利用Rust的内存安全性和高性能特性。
核心特性
- 跨平台支持:一套代码可编译为Web、桌面(通过TAURI)和移动端应用
- React式语法:使用类似JSX的RSX语法编写组件
- 高性能:得益于Rust的零成本抽象和高效的内存管理
- 类型安全:完整的类型系统保障应用稳定性
- 热重载:开发时支持快速重新渲染
安装与设置
首先在Cargo.toml中添加依赖:
[dependencies]
dioxus = "0.3"
dioxus-web = "0.3" # Web平台
# 或 dioxus-desktop = "0.3" # 桌面平台
# 或 dioxus-mobile = "0.3" # 移动平台
基础示例
use dioxus::prelude::*;
fn main() {
dioxus_web::launch(App);
}
fn App(cx: Scope) -> Element {
let mut count = use_state(cx, || 0);
cx.render(rsx! {
div {
h1 { "计数器: {count}" }
button { onclick: move |_| count += 1, "增加" }
button { onclick: move |_| count -= 1, "减少" }
}
})
}
组件开发示例
fn UserCard(cx: Scope, props: UserCardProps) -> Element {
cx.render(rsx! {
div {
class: "user-card",
img { src: "{props.avatar}", alt: "用户头像" }
h2 { "{props.name}" }
p { "{props.bio}" }
}
})
}
#[derive(Props)]
struct UserCardProps {
name: String,
avatar: String,
bio: String,
}
状态管理
fn TodoApp(cx: Scope) -> Element {
let mut todos = use_ref(cx, Vec::new);
let mut new_todo = use_state(cx, || String::new());
cx.render(rsx! {
div {
h1 { "待办事项列表" }
input {
value: "{new_todo}",
oninput: move |e| new_todo.set(e.value.clone()),
}
button {
onclick: move |_| {
todos.write().push(new_todo.get().clone());
new_todo.set(String::new());
},
"添加"
}
ul {
for todo in todos.read().iter() {
li { key: "{todo}", "{todo}" }
}
}
}
})
}
跨平台编译
针对不同平台的编译命令:
# Web平台
cargo dioxus serve
# 桌面平台(需要TAURI)
cargo tauri dev
# 移动平台
cargo mobile init
最佳实践
- 组件拆分:将UI拆分为小型可复用组件
- 状态提升:将状态提升到合适的父组件
- 错误处理:使用Result和Option正确处理可能失败的操作
- 性能优化:使用mem::replace避免不必要的克隆
- 条件渲染:利用Rust的if表达式进行条件渲染
Dioxus为Rust开发者提供了现代化的UI开发体验,结合了React的开发模式和Rust的性能优势,是构建跨平台应用的优秀选择。
完整示例demo
use dioxus::prelude::*;
fn main() {
// 启动Web应用
dioxus_web::launch(App);
}
// 主应用组件
fn App(cx: Scope) -> Element {
// 使用状态钩子管理计数
let mut count = use_state(cx, || 0);
cx.render(rsx! {
div {
// 用户信息卡片组件
UserCard {
name: "张三".to_string(),
avatar: "https://example.com/avatar.png".to_string(),
bio: "Rust开发者".to_string()
}
h1 { "计数器: {count}" }
button {
onclick: move |_| count += 1,
"增加"
}
button {
onclick: move |_| count -= 1,
"减少"
}
// 待办事项组件
TodoApp {}
}
})
}
// 用户卡片组件
fn UserCard(cx: Scope, props: UserCardProps) -> Element {
cx.render(rsx! {
div {
class: "user-card",
img {
src: "{props.avatar}",
alt: "用户头像"
}
h2 { "{props.name}" }
p { "{props.bio}" }
}
})
}
// 用户卡片属性结构
#[derive(Props)]
struct UserCardProps {
name: String,
avatar: String,
bio: String,
}
// 待办事项应用组件
fn TodoApp(cx: Scope) -> Element {
// 使用引用钩子管理待办事项列表
let mut todos = use_ref(cx, Vec::new);
// 使用状态钩子管理新待办事项输入
let mut new_todo = use_state(cx, || String::new());
cx.render(rsx! {
div {
h1 { "待办事项列表" }
input {
value: "{new_todo}",
oninput: move |e| new_todo.set(e.value.clone()),
placeholder: "输入新的待办事项"
}
button {
onclick: move |_| {
if !new_todo.get().is_empty() {
todos.write().push(new_todo.get().clone());
new_todo.set(String::new());
}
},
"添加待办"
}
ul {
for (index, todo) in todos.read().iter().enumerate() {
li {
key: "{index}",
"{todo}"
}
}
}
}
})
}