当前位置: 首页 > ds >正文

rust 全栈应用框架dioxus

逛github时发现了一个号称全栈应用框架dioxus,适用于web / desktop / mobile。零配置、集成了热启动和基于信号的状态管理。是由rust编写的,所以也就不受平台限制。

既然说的这么好,那就来试试构建一下三种平台的应用,构建的应用编译成webdesktopmobile三个平台的应用。

在这里插入图片描述

与其他跨平台框架对比

这里列出几个大家熟知的平台,这些差异可以dioxus官网看到。

dioxus的定位:

  • 使用组件、属性、钩子构建UI视图,状态管理更像Svelte
  • 页面编写保持和HTML、CSS一致
  • 可以任意切换适配目标平台渲染器进行渲染。
  • 有很多功能库持续建设,和dioxus保持同步更新。

对于使用dioxus进行开发,有以下几点有用的功能:

  • 热更新
  • 具有日志记录、项目模板、代码检查等交互式cli。
  • 集成构建部署不同平台,包括webmacosioswindows
  • 支持现代web开发方式,如SSR、混合开发、html流
  • 直接访问系统API,如JNI(android) 、CoreFoundation (Apple) 、web-sys(web)
  • 类型安全的应用程序路由和服务端功能。

VS Tauri

tauri 是基于web框架(如:react、vue、svelte等)的构建移动应用程序框架。

  • 限制了它只能使用js或webassembly;而dioxus可以让你使用线程、访问文件系统等操作,无需IPC桥接。
  • 限制了它只能作用于js;而dioxus可以提供其他能力:后台服务、打包工具、原生渲染器。
  • 它们也有共享的一些库,是由tauri维护的

VS Leptos

leptos 是一个rust框架,它使用webassembly来构建web应用程序。

  • 它使用信号系统驱动响应和渲染。而dioxus使用信号仅驱动响应。
  • 它专注web,提供了一些web平台的组件;而dioxus目标是全平台包括桌面、移动、web。
  • 它使用类似html的语法;而dioxus自定义了一套自己的DSL语法。

VS Yew

yew 是一个构建单应用web程序框架。它是dioxus的灵感来源,而dioxus为了支持更多的平台和功能。yew并不满足。

VS Electron

electron是使用js、html、css等web技术开发桌面应用程序的框架。

  • dioxus 使用原生的webview渲染,它们的构建产物体积相差巨大。dioxus还可以和主机共享系统资源。
  • 相比electron已经很成熟了,而dioxus still quite young。

VS egui

egui 是一个跨平台的GUI库。

  • 它被设计为在每一帧上重新绘制,适用于游戏或其他交互式应用。而dioxus只会绘制一次,然后在每一帧上修改,这也使得dioxus可以使用web原生技术。
  • 它提供了自己的样式和布局方案。而dioxus则可以让你使用适用于web的第三方样式库,比如tailwindcssmaterial-ui
  • 它的状态管理是基于全局单一对象的。而dioxus鼓励使用组件和props传递状态。

通过这些比较呢,可以初步看到dioxus具有的优势。因为我还没有使用过svelte,对于这种基于信号的状态管理还是比较陌生。

构建Hello world

通过以上对比,我们可以看到想要学习使用dioxus,必须学习rust。如果没有学习过rust,那可能需要先去学习rust.

还有一个最重要的区别就是dioxus自己设计了一套DSL语法,类似html标签和css的语法,但可能在使用上还是有些不习惯。但是毕竟不破不立,想要跨平台、想要速度快,如果局限于现有某个技术,那么你也会有这个技术的瓶颈限制。

环境配置

  • 本地安装rust http://rust-lang.org/
  • 选择编辑工具,我这里选择了vscode,并安装了语法提示插件rust-analyzer
  • 安装cargo-binstall,使用它直接安装dx而无需从源码编译。
  • 安装dioxus-cli套件,包括两个部分:dioxus核心库和dx命令行工具。

系统环境配置,因为我是mac,所以无需配置,其他系统请查看Platform-specific dependencies;

初始化项目

dioxus提供了dx命令行工具,用于创建、编译、运行、调试项目。

新创建项目可以使用dx new <project-name>,因为我已经创建好了文件夹,所以需要初始化dx init

在这里插入图片描述

初始化项目时可以选择项目模板,我们选择最简单的模板第一个就行。后续的设置路由、样式都先不需要,最后完成初始化。

在这里插入图片描述

和rust项目比较,多了一个Dioxus.toml用于配置dioxus项目。默认安装了dioxus-webdioxus-desktopdioxus-mobile,且默认平台为web。

编写我们的hello world

清空模板里的代码,输出Hello world!在页面上.

在主入口文件main.rs:

use dioxus::prelude::*;fn main() {dioxus::launch(App);
}#[component]
fn App() -> Element {rsx! {h1 { "Hello, world!" }}
}

我们这行dx serve 等待编译完成,第一次编译会慢一点,后续就会快很多。

在这里插入图片描述

我们访问web服务地址http://127.0.0.1:8080

可以看到页面输出了Hello world!,可以继续我们的开发了 ✊

在这里插入图片描述

可以看到web服务已经可以访问成功了,我们编译为桌面应用,使用dx serve --platform desktop

dioxus提供了开发桌面、移动应用,可以直接在系统上构建GUI页面展示,我们执行命令后,默认直接打开窗口。

在这里插入图片描述

构建移动应用ios的app,对于移动端的开发,需要本地安装XCode 开发工具,并且需要选择下载ios资源包。

在这里插入图片描述

本地rust需要安装目标编译资源

rustup target add aarch64-apple-ios aarch64-apple-ios-sim

所有环境准备好之后,我们启动xcode启动一个ios的模拟器。然后在dioxus项目中执行dx serve --platform ios,它会自动探测到模拟器并安装应用。

在这里插入图片描述

dioxus 核心人员都是全职在维护,感觉应该还是比较靠谱的。

目前版本最新0.6.3 发展过程中可能存在较大更新,但是值得关注。

基础知识

开始编写应用程序的最重要的就是掌握界面设计编码,作为前端开发人员,对于html必不陌生,对于dioxus,为了方便跨平台构建而不受已有语言的限制,自定义了一套自己的DSL语法。新的语法会带来一些陌生、不适以及潜在的问题,但也伴随无限的可能性。

组件

界面设计实现是由一个个组件组成的,组件可复用,可组合。在之前的例子中 App就是一个组件,它是一个函数,返回一个Element。使用rsx!{ }宏来定义UI结构。

#[component]
fn App() -> Element {rsx! {h1 { "Hello, world!" }}
}

我们还使用了#[component]它可以方便我们创建组件,用来简化组件接收到的数据结构定义。如果不使用,我们需要为组件定义参数结构体,并指定参数传递。

根组件App不能接受参数。


#[derive(Props, Clone, PartialEq)]
struct AppProps {name: String,
}fn AppHeader(props: AppProps) -> Element {rsx! {h1 { "Hello, {props.name}!" }}
}

我们定义的结构体AppProps必须实现Props trait,并且支持ClonePartialEq trait。为了简化这一行为,方便创建组件,提供了#[component]derive简化这一过程,无需再定义结构体;还可以帮助我们检查组件定义的正确性。

#[component]
fn AppHeader(name: String) -> Element {rsx! {h1 { "Hello, {name}!" }}
}

通过#[component] 可以方便定义组件接收的参数。定义要接收的参数,无需再手动解构。

dioxus 是声明式框架,通过定义组件状态、属性来决定组件的的行为。

rsx!{ }

通过使用rsx!{ } 宏来定义UI结构,和html一样,我们可以使用比如div h1~h6 img等熟知的标签来构建页面结构。

rsx!{ } 使用rust的结构体定义元素结构,在声明标签结构时,采用的严格模式语法解析。这也在开发模式下提前暴露问题,从而避免运行时错误。

let handle_click = move |event| {info!("Button clicked!");
};rsx! {div {class:"flex flex-col gap-15px",h2 { "Welcome to {name}" }button {onclick: handle_click,"Click me",}}
}

使用变量时,直接使用{ }包裹,rsx会解析使用format!()处理字符串。元素的事件绑定和react是一致的,通过on+事件名绑定事件处理函数。事件处理函数是一个闭包函数,接收事件句柄对象

条件/循环渲染

条件渲染可以通过rust的数据类型bool类型变量,或者Option<>类型的值来判断。

let show_title = true;rsx! {{show_title.then(|| rsx!{ h2 { "Welcome to {name}" }})}
}

也可以通过if 语句进行判断

rsx! {if show_title {h2 { "Welcome to {name}" }}
}

对于循环渲染,可以使用迭代器.map 或者for .. in 进行渲染

rsx! {ul {{(0..3).map(|i| rsx!{ li {"index {i}"}})}}ul {for i in 0..3 {li {"index {i}"}}}
}

条件渲染和循环渲染,通常在业务中变量是动态的,比如通过点击事件更新变量值,而我们直接使用rust声明的变量值在修改时会存在所有权转移的问题,在上文中,点击事件的回调是一个闭包,想要在里面修改,并试图让视图更新,变量的所有权问题导致变得复杂。

在后面的章节 状态管理 中dioxus提供和视图绑定的hooks帮助我们管理视图状态,比如我们通过use_signal来控制状态,然后更新变量,从而重新渲染视图。

let mut show_title = use_signal(|| false);
let handle_click = move |event| {show_title.set(!show_title());
};rsx! {div {if show_title() {h2 { "Welcome to {name}" }}button {onclick: handle_click,"Click me",}}}

加载静态资源

dioxus 提供了assets!()来加载静态资源,比如如图片、css、js、json等。

在目录assets下定义css样式文件style.css,并导入到main.rs中。

static STYLE_CSS: Asset = asset!("/assets/css/style.css");

样式导入后需要将它和组件绑定在一起,通过document::Stylesheet {}引入,和html的link标签作用是一样的。

rsx! {document::Stylesheet {href:STYLE_CSS}// ...
}

加载图片也是同样的使用asset!,让后绑定到元素的属性上。

rsx! {img { src:BG_IMAGE, width:300,height:150}
}

通过/访问项目的根目录,不能直接将资源路径赋值给标签属性,这样无法加载到资源。

通过asset!加载资源还做了一些优化措施,我们也可以通过第二个参数设置来优化资源,比如通过将png转换为avif.

static BG_IMAGE: Asset = asset!("/assets/imgs/dioxus.png",ImageAssetOptions::new().with_avif()
);

可以看到本来500多kb的png图片转换为avif后只有不到100kb了,提升了的图片的加载速度。

状态管理

rust中有所有权的概念,我们创建一个变量,在一个地方使用后,再在另一个地方使用会报错所有权被转移的错误。为了在其他地方使用我们可以借用变量,只使用它的值;或者调用.clone()来创建一个变量的副本。

dioxus提供了一个hookuse_hook函数处理这种情况,在每一次使用时,都会返回.clone()的值。

let name = use_hook(|| "hboot");

use_hook接受一个闭包函数,用于初始化渲染时创建变量。它不是响应式的,只能用来共享变量值,如果需要响应式变量,则需要使用use_signal

use_signal的设计来源于Svelte,dioxus的组件和react的组件一样,响应式数据发生变更时,就会重新渲染组件。创建的响应式变量可以通过.set()方法来更新值,

#[component]
fn AppFooter() -> Element {let mut count = use_signal(|| 0);rsx! {button {onclick: move |_| count+=1,"Clicked {count} times"}}
}

对于响应式数据,我们经常会遇到的父子组件共享数据,我们可以将use_signal创建的变量传递给子组件,而对于需要共享更多的组件,跨更深的层级,一直传递不好维护。

dioxus提供两种方式共享数据:ContextGlobalSignal

Context 局限于有上下级关系的组件,它提供了一个共享的上下文数据。通过use_context_provider创建数据对象,在子孙组件中通过使用use_context来获取消费数据。

我们在App组件中,创建一个Context对象,并在后续组件中使用。

#[component]
fn App() -> Element {use_context_provider(|| "Dioxus-app");rsx! {AppFooter {}}
}

我们提供了一个类型&str的变量值,在AppFooter来获取消费。可以看到我们在获取变量时只定了类型&str,这里是必须的,它帮助在上文环境中找到对应的Context,如果定义了的类型找不到,则会报错。

#[component]
fn AppFooter() -> Element {let app_name: &str = use_context();rsx! {footer {class:"bg-red-100 flex flex-col gap-15px",h2 { "Footer {app_name}" }}}
}

可以配合use_signal创建响应式变量,当变量发生更新时,所有依赖的组件都会重新渲染。

let app_name = use_signal(|| "Dioxus-app");
use_context_provider(|| app_name);

在获取消费时,它的类型变成了Signal<&str>

GlobalSignal 是一个全局共享的信号,它允许多个组件共享一个变量,并且当变量发生变化时,所有依赖它的组件都会重新渲染。通过Signal::global创建一个全局响应式变量,可以通过.write()方法来更新变量的值。

static APP_NAME: GlobalSignal<&str> = Signal::global(|| "Dioxus-app");

在更新变量时,通过.write() 获取可变引用,然后*解引用指向值存储地址修改值。

*APP_NAME.write() = "Rust";

除了这些本地同步执行的变量初始化,还有异步请求获取的数据,因为是异步的,它会在一段时间后初始化完毕,需要我们记录状态,dioxus提供了use_resource函数来帮助我们管理异步数据状态,它提供了开始、暂停、停止等方法来控制。

use_resource(|| async move {// 异步请求
});

Hooks

dioxus 借鉴了react的hooks概念,提供了一些Hook,用于管理组件状态。在上一章节中使用了几个常用的hook,当然还有一些hook供我们使用

Hook使用规则

dioxusHook使用规则同react的hooks限制,只能在组件body中使用,不能在函数或其它语句块中使用。约定俗成使用use_自定义hook。

  • 不能在条件语句中使用
  • 不能循环语句中使用
  • 不能在闭包中使用

响应式变量不能直接在组件体内修改,可以在副作用hook中修改,比如use_effect,它会在组件初始渲染时执行一次;且会收集其中使用到的响应式变量,当变量发生变化时,组件重新渲染,也会再次执行它。

let mut count = use_signal(|| 0);// 不要直接修改
count+=1;// 在副作用hook中修改
use_effect(move || {count+=1;
})

use_hook

use_hook 不是一个响应式声明变量的hook,它提供创建一个值,以便我们可以在组件内部使用它。解决了rust变量的所有权限制。

let count = use_hook(|| 0);

use_signal

use_signal 是一个响应式声明变量的hook,组件会跟踪值的变化,当值变化时,组件会重新渲染。

let mut count = use_signal(||0);use_effect(move || {count+=1;
})

当声明了一个Signal变量后,提供一些方法在不同的情况下来操作这个变量。

// 获取到变量的一个克隆值
let value:i32 = count();// 获取变量值的引用
let value:&i32 = &count.read();// 设置变量的值
count.set(10);// 获取内部值的一个可变引用,然后解引用并修改值
let value:&mut i32 = &mut count.write();
*value = 10;

use_effect

use_effect 是一个副作用可执行的hook函数,在组件初始渲染时执行一次;并且会收集在其内部使用到的响应式变量,当它们发生变化时,组件重新渲染,也会再次执行它。

use_resource

use_resource 是一个执行异步任务的hook函数。

use_memo

use_memo 是一个存储缓存值的hook,由其它变量计算一个可被跟踪的计算值。依赖的响应式变量发生更改时,计算一个新值,判断是否和之前的值相同。

let dobule_count = use_memo(move || count*2);

更新值和旧值相同时,则不会更新组件。

use_reactive

use_reactive 可以将一个原始数据值转为响应式变量,并跟踪原始值的变化。

可用于父子组件传递数据时,父组件传递了一个响应式变量的值引用,子组件通过use_memo使用了未被跟踪的值,即使值发生变化,use_memo的值也不会更新。

#[component]
fn Child(count: i32) -> Element {let double_count = use_memo(move || count * 2);rsx! {div {"Count: {double_count}"}}
}

为了处理这种情况,通过use_reactive转换为可跟踪的响应式变量。

#[component]
fn Child(count: i32) -> Element {let double_count = use_memo(use_reactive(&count, |val| val * 2));rsx! {div {"Count: {double_count}"}}
}

提供了一个use_reactive!简化书写依赖,可从闭包函数中获取需要依赖跟踪的值。依赖多个变量时,可使用元组()传递。

#[component]
fn Child(count: i32) -> Element {// let double_count = use_memo(use_reactive(&count, |val| val * 2));let double_count = use_memo(use_reactive!(|count| count * 2));rsx! {div {"Count: {double_count}"}}
}

use_context_provider

use_context_provider是一个用于创建一个上下文数据的hook,它允许在组件树中传递数据。子孙组件可以通过use_context进行消费。

最关键的是变量的数据类型,它是根据类型推导出需要获取上下文的哪个变量数据。

use_drop

use_drop 是一个用于清理资源的hook,在组件销毁时执行清理操作。组件默认会清理hooks、副作用函数,也可以自定义清理操作。

use_drop 可以在服务端渲染时被调用。

还提供其它很多不同地hooks 但由于dioxus仍在设计中,未来不确定性会被移除。

引用

  • dioxus

  • dioxus-doc

http://www.xdnf.cn/news/3140.html

相关文章:

  • 深入解析常见排序算法及其 C# 实现
  • 系统思考培训助力总经理
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年4月29日第67弹
  • RISE with SAP 的合同及许可解析
  • 【电子对抗训练革命】新一代便携式雷达模拟器技术解密
  • Spring事务开发经验 回滚和不回滚?
  • ADS1299模拟前端(AFE)代替芯片——LHE7909
  • C事件驱动网络库​​libevent的http详解
  • Java实现使用EasyExcel按模板导出文件
  • 【Unity】使用LitJson保存和读取数据的例子
  • SQL注入
  • Leetcode 3533. Concatenated Divisibility
  • 【C到Java的深度跃迁:从指针到对象,从过程到生态】第四模块·Java特性专精 —— 第十七章 IO流:超越FILE*的维度战争
  • SpringBoot之SpringAl实现AI应用-快速搭建
  • LeetCode -160.相交链表
  • “假读“操作在I2C接收流程中的原因
  • DECAP CELL
  • Qt入门——什么是Qt?
  • 【Linux】第十三章 访问Linux文件系统
  • React:封装一个编辑文章的组件
  • python如何流模式输出
  • Missashe考研日记-day30
  • JR6001语音模块详解(STM32)
  • 1.3 点云数据获取方式——ToF相机
  • Linux电源管理(3)_关机和重启的过程
  • 【今日三题】小红的ABC(找规律) / 不相邻取数(多状态dp) / 空调遥控(排序+二分/滑动窗口)
  • 面向人工智能、量子科技、人形机器人等产业,山东启动制造业创新中心培育认定
  • Android Studio 中实现方法和参数显示一行
  • Git 多账号切换及全局用户名设置不生效问,GIT进行上传无权限问题
  • 科研入门规划