Dioxus 做桌面应用:和 Tauri 一样是 WebView,但写起来不是一回事

前言

前面几篇我们基本都在 Web 视角里看 Dioxus:

  • rsx! 怎么写
  • Signals 怎么驱动状态
  • 组件怎么拆
  • 路由怎么组织

但 Dioxus 真正有意思的地方,不只是"Rust 也能写页面"。

它最容易让人记住的一点其实是:同一套 Rust UI,浏览器里能跑,桌面窗口里也能跑。

问题也正出在这里。

很多人一看到"桌面端 + WebView",立刻就会联想到 Tauri,然后顺手给个结论:

哦,懂了,就是和 Tauri 一样,包个 WebView。

这话只说对一半。

因为两者的共同点确实很明显:UI 都是跑在系统 WebView 里的。但差别也很明显:Tauri 通常是前端一套、Rust 一套;Dioxus 则尽量把 UI、状态和组件这几件事留在同一个 Rust crate 里。

所以这篇不只讲"怎么执行一条命令把桌面窗口弹出来",更想讲清 4 件事:

  1. Dioxus Desktop 的运行链路到底是什么
  2. 它和 Tauri 明明都用 WebView,为什么写起来不像一回事
  3. 桌面能力怎么接进来,边界又在哪里
  4. 真要落项目,什么时候它合适,什么时候你该换路线

1. 先说结论:Dioxus Desktop 不是原生控件渲染,而是原生窗口 + 系统 WebView

先把最关键的一句摆前面:

Dioxus Desktop 不是"Rust 直接画原生按钮、原生输入框",而是"Rust 写组件树,最后把界面放进一个原生窗口里的 WebView"。

如果把它拆开看,大致是这 4 层:

  1. 你写的是 Rust 组件,入口通常还是 main.rs
  2. 组件里的 UI 语法是 rsx!
  3. 桌面端运行时会创建一个原生窗口
  4. 窗口内部嵌的是系统 WebView,真正显示出来的仍然是 HTML/CSS 渲染结果

也就是说,Dioxus Desktop 的"桌面",本质不是像 Qt、Makepad 那样自己画控件,而是:

  • 外面是原生窗口
  • 里面是浏览器渲染引擎
  • 你写 UI 的语言不是 HTML/JS,而是 Rust

这个认知最好先立住,因为它会直接影响你后面对很多问题的预期:

  • 为什么 CSS 依然很好使
  • 为什么布局思路很像前端
  • 为什么复杂动画和大列表的瓶颈,很多时候还是 WebView
  • 为什么它和 Tauri 的性能边界有相似之处

2. dx serve 到底帮你做了什么

先看最直接的命令。

在前面的入门篇里,我们已经跑过 Web 版:

bash 复制代码
dx serve

切到桌面端时,常见写法是:

bash 复制代码
dx serve --platform desktop

如果你本机 CLI 显示的是:

bash 复制代码
dx serve --desktop

那就以你当前 dx --help 为准。不同阶段的 CLI 参数写法可能会有变化,但核心动作没变:把当前项目切到桌面目标来启动。

很多人把这一步理解成"把 Web 项目装进一个桌面壳子里"。这么理解不算全错,但还是太粗。完整一点的运行链路,大概是这样:

2.1 第一步:dx 还是在调 Rust 编译链

dx 只是 Dioxus 官方给你准备的一层入口,底层绕不开的仍然是 Rust 工具链。也就是说:

  • 项目依然是 Cargo 项目
  • 依赖依然在 Cargo.toml
  • 代码改动后依然要经过 Rust 编译

这一点和很多纯前端打包器完全不同。

所以你在桌面端感觉"还挺顺",不代表它脱离了 Rust 编译,只是 Dioxus 把这条链路包得更顺手了。

2.2 第二步:桌面运行时创建原生窗口

编译通过后,Dioxus Desktop 会启动桌面运行时,创建一个真正的系统窗口。

这意味着你拿到的不是浏览器标签页,而是:

  • Windows 上的独立窗口
  • macOS 上的独立 App 窗口
  • Linux 上的独立桌面窗口

这一层是"桌面感"的来源。

2.3 第三步:窗口里嵌入系统 WebView

窗口有了,但窗口本身不会画你的页面。

真正负责渲染的,是系统提供的 WebView 引擎。你可以把它理解成"应用内部自带的一块浏览器显示区",只不过这个浏览器不是你手动打开的 Chrome 标签页,而是桌面程序里嵌进去的渲染区域。

这一步也是 Dioxus Desktop 和 Tauri 的共同地基。

2.4 第四步:Dioxus 把组件更新同步到 WebView 里的 DOM

你写的 rsx!、信号状态、事件处理,最终还是要映射成浏览器能理解的那套东西。

所以当状态变化时,Dioxus 会重新计算相关组件,再把变更同步到 WebView 里的页面结构上。

你可以把它理解成:

  • 上层心智是 Rust 组件
  • 底层渲染目标仍然是浏览器 DOM

这也是为什么前几篇里学到的很多东西,切到桌面端几乎不用重学。

3. 为什么同一个项目几乎不用改,就能从 Web 切到 Desktop

这一点最好用代码看。

先放一个很普通的 Dioxus 页面:

rust 复制代码
use dioxus::prelude::*;

fn main() {
    dioxus::launch(App);
}

#[component]
fn App() -> Element {
    let mut name = use_signal(|| String::from("Dioxus Desktop"));
    let mut clicks = use_signal(|| 0);

    rsx! {
        div { class: "page",
            h1 { "桌面端也能直接复用这套组件" }
            p { "你好,{name}。" }

            input {
                value: "{name}",
                oninput: move |evt| name.set(evt.value()),
                placeholder: "改个标题试试"
            }

            button {
                onclick: move |_| clicks += 1,
                "点了 {clicks} 次"
            }
        }
    }
}

这段代码有两个很关键的特点:

  • 它没有写任何桌面专属 API
  • 它也没有多一个前端项目目录

你跑 Web:

bash 复制代码
dx serve

它就是浏览器里的页面。

你跑桌面:

bash 复制代码
dx serve --platform desktop

它就是一个桌面窗口里的页面。

这里最关键的一点是:平台切换发生在运行目标层,而不是"我要不要换一套 UI 代码"这一层。

如果你是从 React + Tauri 的组合过来的,这种感觉会非常明显。

在 Tauri 里,最常见的组织方式通常是:

  • 前端项目一套
  • Rust 后端一套
  • 中间靠 command / event / IPC 通信

而在 Dioxus 里,很多时候你写的只是:

  • 一个 Rust 项目
  • 一套组件
  • 一套状态
  • 根据平台切不同运行时

所以我一直觉得,它和 Tauri 虽然都站在 WebView 上,但写起来确实不是一类东西。

4. Dioxus Desktop 和 Tauri 的差别,不在"是不是 WebView",而在"代码边界画在哪"

先把结论写前面:

Tauri 解决的是"用 Rust 给现有前端应用加一个轻量桌面壳";Dioxus Desktop 更像是在解决"我想把整套 UI 和业务状态都留在 Rust 里"。

看一张对比表会更直观:

维度 Dioxus Desktop Tauri
渲染载体 系统 WebView 系统 WebView
UI 主要写法 Rust 的 rsx! 组件 HTML / CSS / JS 或前端框架
项目组织 通常一套 Rust 项目 往往是前端项目 + Rust 项目
跨语言边界 更少 更明显
前端生态复用 CSS 思路能复用,JS 生态复用少 前端生态几乎原样复用
适合团队 Rust 主导 前端 + Rust 协作

4.1 Dioxus 的好处:单语言栈,切换更少

如果你是 Rust 开发者,Dioxus 很顺的地方在于:

  • 组件是 Rust
  • 状态是 Rust
  • 事件处理是 Rust
  • 路由还是 Rust

你不需要前一秒在 JSX 里改页面,后一秒再回 Rust 改 command,然后再处理两边的参数类型对齐。

说白了,它减少的是语言切换成本,不是 WebView 成本。

4.2 Tauri 的好处:前端生态来得更完整

但反过来说,Tauri 的优势也很明确。

如果你的团队本来就有成熟前端栈,比如:

  • React
  • Vue
  • Svelte
  • Tailwind CSS
  • 一整套现成组件库

那 Tauri 会更直接。因为它不要求你把 UI 重写成 Rust 组件,前端同学几乎可以原封不动把现有能力带过来。

所以这两个工具不是谁"更先进",而是解决的问题不太一样:

  • 想保留前端工程体系,看 Tauri
  • 想把 UI 和业务状态尽量收进 Rust,看 Dioxus

5. 热更新体验为什么比想象中顺一点

很多人第一次切到 Dioxus Desktop,会有点意外:

Rust 写桌面 UI,为什么改个文案、样式、组件结构,反馈还挺快?

原因不复杂,因为你改的大量内容,本质上还是页面层的东西。

前面第 2 篇其实已经提过一遍,放到桌面端依然成立:

  • rsx! 内容,反馈通常不错
  • 改 CSS,反馈通常不错
  • 改小块逻辑代码,也往往还行
  • 改依赖、改 Cargo.toml、改大结构,还是得老老实实等编译

所以 Dioxus Desktop 的开发体感,某种意义上是两层东西叠在一起:

  1. 上面那层是前端式的页面修改反馈
  2. 下面那层仍然是 Rust 编译链

我对它的评价一直比较克制:它的反馈速度确实比"纯 cargo run 桌面 GUI"舒服很多,但也别把它想成脚本语言那种秒改秒生效。

预期摆正,体验会好很多。

6. 桌面专属能力怎么接进来

讲到这里,很多人会追问一句:

页面能跑进桌面窗口我懂了,那桌面程序该有的能力怎么办?

如果 Dioxus Desktop 只能把网页塞进窗口里,那它价值其实不大。真正让它像桌面程序的,是你可以继续往里接桌面能力。

6.1 文件对话框:这是最常见、也最实用的一类能力

举个例子,做一个桌面笔记应用、导入工具、图片处理器,十有八九都会遇到"让用户选文件"。

这类能力在桌面端很常见,配合 Rust crate 往往就能接进去。比如下面这个思路:

rust 复制代码
use dioxus::prelude::*;
use rfd::FileDialog;

fn main() {
    dioxus::launch(App);
}

#[component]
fn App() -> Element {
    let mut picked = use_signal(|| String::from("还没选择文件"));

    rsx! {
        div {
            h1 { "桌面文件选择示例" }
            button {
                onclick: move |_| {
                    if let Some(path) = FileDialog::new().pick_file() {
                        picked.set(path.display().to_string());
                    }
                },
                "选择文件"
            }

            p { "{picked}" }
        }
    }
}

这段代码想说明的不是 API 长什么样,而是一个很实际的事实:

你在 Dioxus Desktop 里接桌面能力,很多时候并不需要先穿过一个前端到 Rust 的 IPC 通道,因为你的 UI 本来就写在 Rust 里。

这点在实际开发里非常省心。

6.2 窗口控制、标题、尺寸、关闭行为

除了文件对话框,桌面程序还经常会碰到这些需求:

  • 改窗口标题
  • 控制初始宽高
  • 最小化、最大化、隐藏
  • 关闭窗口时改成托盘常驻而不是直接退出

这类能力通常会落在两类位置上:

  1. 启动阶段的桌面配置
  2. 运行中的窗口句柄或桌面上下文

你可以把它理解成:

  • 静态配置:应用一启动就确定,比如默认窗口大小、标题、图标
  • 动态控制:应用跑起来以后根据交互去改,比如点按钮最小化窗口

从这里开始,Dioxus 才真的有点桌面应用的味道。

6.3 系统菜单、托盘、原生集成

再往下走,就是更偏桌面生态的能力:

  • 系统菜单
  • 托盘
  • 原生快捷键
  • 平台相关行为

这些能力通常能做,但有个预期最好先摆正:Dioxus Desktop 的桌面原生能力覆盖,不太适合按"和 Tauri 完全同级"去理解。

Tauri 这些年在桌面集成这一层做得更深、更成熟;Dioxus 的核心优势仍然是"Rust UI 跨平台"这条线,而不是"原生壳能力卷到最全"。

所以如果你的项目高度依赖:

  • 非常完整的系统菜单
  • 很复杂的托盘交互
  • 深度平台特性
  • 大量成熟插件

那就要认真比较 Dioxus 和 Tauri,而不是只看"都能弹个窗"。

7. 打包怎么理解:dx bundle 解决的是"分发",不是"神奇跨编译"

开发阶段能跑起来,只能说明一半。

真正到项目往外发的时候,你迟早会碰到打包:

bash 复制代码
dx bundle --platform desktop

它的目标很明确,就是把当前桌面应用整理成适合分发的桌面产物。

常见可以期待的结果,大致会是:

  • macOS 的 .app / .dmg
  • Windows 的 .exe 或安装器形态
  • Linux 的 AppImage 等发行物

但这里一定别想得太轻松。

7.1 打包不是"一条命令就跨三平台白嫖"

dx bundle 很好用,但它并不意味着你在一台机器上随手一敲,就能完美产出所有平台包。

桌面打包通常还会受这些现实条件影响:

  • 当前宿主系统
  • 目标平台工具链
  • 图标和资源配置
  • 代码签名
  • 平台本身的打包规范

尤其是 macOS 和 Windows,这两边各有各的规矩,不是"Rust 能 cross compile"就一切自动搞定。

7.2 Dioxus.toml 更像桌面分发配置入口

到这一步,Dioxus.toml 的存在感就会明显增强。

它不只是个"顺手带着的配置文件",很多桌面应用关心的信息,都会往这里收:

  • 应用名
  • 图标
  • bundle 相关参数
  • 静态资源声明

所以如果你前面一直把它当成可有可无的文件,到桌面打包这里基本就该认真看它了。

8. 性能和体积,到底该怎么和 Tauri 比

这一段很容易在评论区吵起来,所以先把话说死一点:下面不是谁吊打谁,只是把 Dioxus Desktop 放回它实际的位置里。

8.1 先说性能:两边都逃不开 WebView 的天花板

Dioxus Desktop 和 Tauri 既然都基于系统 WebView,那就有一个很现实的共同点:

复杂页面的上限,很多时候不是由"你写的是 Rust 还是 JS"决定的,而是由 WebView 的渲染能力决定的。

比如这些场景:

  • 超大表格
  • 超长虚拟列表
  • 高频动画
  • 大量 DOM 更新

如果页面结构本身就很重,WebView 这一层的约束不会凭空消失。

所以别把"Rust 写 UI"自动等价成"桌面性能一定碾压前端栈"。这不是一回事。

8.2 再说体积:Dioxus 的重点不是比 Tauri 更小,而是更统一

很多人拿 Tauri 的卖点做第一反应:

Tauri 很轻,那 Dioxus 会不会也一样轻?

这得分开看。

两边都站在系统 WebView 上,所以它们都不像 Electron 那样自己带整套 Chromium,这一点先别混淆。

但 Dioxus 的重点并不是"把桌面包做到最小",而是:

  • UI 留在 Rust
  • 代码跨 Web / Desktop / Mobile
  • 尽量减少多语言切换

所以如果你问我该怎么理解体积问题,我的答案很简单:别先比口号,先打出你自己的包。

因为影响桌面包大小的因素非常多:

  • 依赖数量
  • 是否启用额外功能
  • 资源文件体积
  • 构建 profile
  • 平台差异

你真正该比较的不是抽象框架名,而是"同一个业务需求在两套方案里各自产生什么结果"。

8.3 它真正占便宜的地方,在研发路径

如果项目目标是:

  • 一个内部工具
  • 一个 MVP
  • 一个管理后台 + 桌面客户端
  • 一个希望以后还能顺手再上 Web 的产品

那 Dioxus Desktop 的价值很清楚:

它不一定在每个单点指标上都赢,但它能把整条研发路径收得更统一。

这往往比"某项 benchmark 更漂亮"更值钱。

9. 什么场景下我会推荐 Dioxus Desktop

说了这么多,最后还是要落到选型。

我会推荐 Dioxus Desktop 的场景,大概是这几类:

  • 你本来就想用 Rust 做主要开发语言
  • 你不想维护前端 + Rust 两套工程
  • 你需要 Web 和桌面尽量共用一套 UI 与状态逻辑
  • 你的产品更偏工具型、业务型,而不是极致原生 UI 动效型

反过来,如果是下面这些场景,我会更谨慎:

  • 你已经有成熟前端团队和大量前端资产
  • 你要深度吃平台原生壳能力
  • 你非常在意复杂动画、渲染极限或原生控件质感
  • 你希望直接复用整个 JS 组件生态

这个时候,Tauri、Makepad,甚至别的桌面路线,可能都更合适。

总结

如果这篇只留一句话,那就是:Dioxus Desktop 的重点,不是"Rust 也能套 WebView",而是"用 Rust 组件的写法,把 WebView 这条跨平台路线重新组织了一遍"。

它和 Tauri 的共同点,是都站在系统 WebView 上。

它和 Tauri 的真正差别,是:

  • Tauri 更像"前端应用接上 Rust 桌面壳"
  • Dioxus 更像"Rust UI 直接跨到桌面运行时"

所以它的卖点从来都不是"绝对最原生",也不是"绝对最轻"。真正吸引人的地方,是你可以顺着同一套组件和状态模型,把一套 Rust 代码继续铺到 Web、桌面、移动三端。

这也是为什么我觉得 Dioxus 值得单开一个系列来写。

这一篇主要回答的是:Dioxus Desktop 到底怎么跑起来,它和 Tauri 的差别到底落在哪。

下一篇如果继续往下接,一个很自然的话题就是:Dioxus Mobile 能做什么,和桌面这条线复用到什么程度,限制又在哪里。

如果你正在做 Rust 客户端选型,也欢迎把你的场景丢到评论区。工具型应用、内部系统、跨端 MVP,这几类问题最适合拿 Dioxus 来具体聊。