本文是对 Does Dioxus spark joy? 的整理与翻译。
内容结构概览
- 文章背景:这是一篇 Rust Paris Meetup 演讲稿改写,语气更像现场分享。
- Dioxus 是什么:一个 Rust UI / fullstack 框架,目标是一套代码覆盖 Web、移动端、桌面端。
- 名字梗与创业背景:Dioxus 名字“法律上没有受到某个宝可梦启发”,并且是 YC startup。
- Web 应用第一阶段:服务器生成 HTML,浏览器只负责显示。
- Web 应用第二阶段:SPA 兴起,服务器给 JSON/XML,客户端渲染界面。
- SPA 的问题:首屏加载慢、SEO、可访问性、路由、数据加载和请求瀑布。
- 第三阶段:fullstack / SSR + hydration:服务器先渲染 HTML,客户端再接管并变得可交互。
- Dioxus counter 示例:用
#[component]、use_signal、rsx!写一个计数器。 - 服务端渲染时发生什么:服务器输出 HTML 和 hydration hints,但事件处理仍要在客户端注册。
- hydration 的脆弱性:客户端渲染和服务端渲染不一致时,映射会失败,页面可能跳动或重建。
- 异步数据加载问题:数据库、API、组件级数据加载仍然是全栈框架的复杂核心。
- Dioxus 的 hook 生态:同步、异步、响应式、缓存、server-only、client-only hooks 很多,也很吓人。
- hooks 规则问题:违反规则时不一定编译报错,可能只是行为奇怪。
- 作者原本想吐槽 Dioxus:实际深入后发现很多问题是误解、生态限制,或主分支已经修。
dx工具体验:包装 cargo、处理 WASM 编译、提供浏览器加载界面。- panic 体验:老版本 app panic 后可能无提示卡死,主分支已有改进。
- WASM 调试体验:原本 stack trace 很难看,但 Chrome DWARF 扩展让源码行号和断点调试变得可用。
- Subsecond / hot patching:启用后能热更新组件且尽量保留状态,但仍处早期,会崩。
- 结论:与 Svelte 5 相比还不够舒服,但 generational references、server functions、Dioxus 团队方向都让人期待。
- 后记反转:演讲后作者又写了几千行 Dioxus,说明虽然纠结,但已经投入进去了。
这篇文章和 fasterthanlime 平时那些长篇系统工程排障文章不太一样。它不是动态链接器侦探故事,不是 rustc 性能爆炸排查,也不是 Pin、Future、Waker 的底层讲解。它更像一次现场演讲:轻松、带梗、吐槽很多,但背后讲的是一个很实际的问题:
Rust 做前端,到底有没有快乐?
更具体一点,是 Dioxus 到底有没有让人感到快乐。
Dioxus 是 Rust 生态里的一个 UI / fullstack 框架。它的愿景很大:用一套 Rust 代码写 Web 应用、移动应用、桌面应用,甚至前后端都可以放在同一个代码库里。从宣传角度看,这听起来很诱人。React Native、PhoneGap、Flutter、Tauri、Electron、SvelteKit、Next.js 这些名字背后,其实都在追求某种相似目标:尽可能少写重复代码,尽可能多覆盖平台,让开发者不用为每个平台重写一套应用。
但愿景归愿景,真正写起来怎么样?这篇文章的答案不是简单的“好”或“不好”。作者的结论更微妙:
Dioxus 现在还没有 spark joy,但它的方向值得期待。
这篇文章的价值也正在这里。它不是站队,不是营销,也不是单纯吐槽。它从 Web 应用发展史讲起,解释为什么 fullstack 框架本来就复杂;再用 Dioxus 的计数器示例解释 SSR、hydration 和 hooks;最后回到开发体验,评价 dx、WASM 调试、hot patching、panic 反馈、server functions 等能力。读完会发现,Dioxus 的很多痛苦不只是它自己的问题,而是 Rust + WASM + 前端框架 + 全栈开发这个组合本身就很难。
一、Dioxus 到底是什么
文章开头先拿 Dioxus 的名字开了个玩笑。Dioxus 官方曾经说,这个名字“法律上没有受到任何宝可梦启发”。虽然作者又提到,相关人员承认 Deoxys 这个宝可梦确实很酷。
这个开场很 fasterthanlime。先讲一个名字梗,再讲 YC、Huawei 资助、Rust 项目生态里的资金来源玩笑。表面上看是在跑题,其实是在给后面的严肃讨论降温。
抛开玩笑,Dioxus 的核心承诺是:
用一套 Rust 代码,覆盖移动端、Web 端、桌面端。
它还进一步提供 fullstack 模式,也就是客户端和服务端也可以共享一部分代码。这个方向很自然。过去很多框架都在追求类似目标:写一次,到处运行;或者至少写一套组件模型,多个平台复用。React Native 是一种方案,PhoneGap 是一种方案,Flutter 是一种方案,Electron/Tauri 又是另一种方案。
Dioxus 的差异在于,它站在 Rust 生态里。它希望开发者能用 Rust 的类型系统、enum、pattern matching、trait、crate 生态来写 UI,也希望 Rust 最终不只活在后端、CLI、系统工具和嵌入式里,而能进入前端和跨平台 UI。
这听起来很美,但马上会遇到一个问题:Web 应用这些年已经经历了很多代范式变化,而每一次变化都不是无缘无故的。
要评价 Dioxus,就要先理解 Web 应用为什么会变成今天这样。
二、第一代 Web 应用:服务器生成 HTML
最早的一代 Web 应用很简单:服务器负责生成 HTML,浏览器负责显示 HTML。
这时的职责划分很清楚:
服务器:查询数据库,拼 HTML
客户端:接收 HTML,显示页面
浏览器端可以有一点 JavaScript,甚至早年还有 Visual Basic Script,但它不是应用的核心。真正的应用逻辑在服务器上。用户点击链接或提交表单,浏览器发请求,服务器返回新 HTML,浏览器刷新页面。
这套模式有很多优点。首屏通常很直接。搜索引擎容易抓取。浏览器不需要先下载一个巨大应用再渲染内容。表单和链接也有原生行为,退一步讲,即使 JavaScript 出问题,很多页面仍然能基本工作。
但它也有明显缺点。交互体验有限。每次操作都可能刷新整页。复杂 UI 很难做得流畅。页面状态经常随着导航丢失。随着 Web 应用越来越像桌面应用,这种模式开始显得不够用了。
于是进入第二阶段。
三、第二代 Web 应用:SPA 和客户端渲染
第二代 Web 应用是 SPA,也就是 single-page application。服务器不再负责生成完整 HTML,而是更多提供数据,比如 JSON 或 XML。浏览器端下载 JavaScript 应用,由 JavaScript 根据数据渲染 UI。
此时职责划分变成:
服务器:提供 API 和数据
客户端:渲染页面,处理状态,管理路由和交互
这解决了一些问题。应用可以像桌面软件一样流畅切换页面,局部更新 DOM,保存客户端状态,离线缓存也更容易做。开发者也可以用 React、Vue、Svelte 这类组件化框架组织复杂 UI。
但 SPA 也带来了新的痛苦。
首先是初始加载体验。用户打开页面后,可能先看到一个空白页或 loading spinner。浏览器要下载 JS bundle,执行应用代码,再发 API 请求,等数据回来,再渲染 HTML。整个过程可能很慢。于是大家逐渐对 spinner 产生集体厌恶。
其次是 SEO。搜索引擎和社交平台抓取页面时,未必能等完整客户端应用渲染出来。虽然现代爬虫能力更强,但 SPA 的 SEO 仍然比传统 HTML 更麻烦。
然后是可访问性和导航历史。浏览器原生的链接、表单、返回按钮、焦点管理、滚动位置,本来都很成熟。但 SPA 接管了一切后,框架和开发者要重新处理这些细节。一旦处理不好,用户体验就会变差。
再然后是数据加载。如果页面上每个组件都自己发 API 请求,就可能形成请求瀑布。一个页面要加载很多数据,每个子组件都等自己的接口,用户就看着 spinner 一层层转。更糟糕的是,如果组件副作用写错,可能进入无限请求循环。
于是第三阶段出现:服务端渲染加客户端 hydration。
四、第三代 Web 应用:SSR + hydration
所谓 SSR,也就是 server-side rendering。它试图结合前两代的优点。
服务器先根据数据渲染 HTML,把页面尽快发给客户端。浏览器可以立刻显示页面内容,不用等完整 JavaScript app 下载和执行。与此同时,服务器还会把渲染时用到的结构化数据一起发给客户端。客户端加载应用后,重新执行一遍渲染逻辑,把自己的虚拟树和服务器发来的 HTML 对上,然后接管事件处理,让页面变成交互式。
这个“接管”过程就是 hydration。
可以把它粗略理解为:
1. 服务器先渲染 HTML,让用户尽早看到页面。
2. 客户端拿到同样的数据。
3. 客户端重新渲染一份组件树。
4. 框架把客户端组件树和服务器 HTML 对应起来。
5. 框架安装事件处理器,让页面变成交互式。
听起来很合理,但细节非常多。
服务端渲染出来的 HTML 只是一份静态结构。按钮上不能真的携带 Rust closure,也不能把服务器上的事件处理函数直接传给浏览器。服务端只能留下某些 hint,让客户端之后知道:这个按钮对应客户端渲染树里的哪个节点,它需要绑定哪个事件。
更麻烦的是,如果服务器渲染和客户端渲染不一致,hydration 就会失败。比如服务器认为按钮文本是 “+”,客户端渲染出来却是 “-”;服务器渲染三个节点,客户端渲染四个节点;服务器生成的列表顺序和客户端不同。这时框架很难建立映射,只能放弃某些节点,甚至整个重建。用户看到的可能就是页面突然跳动。
所以 fullstack UI 框架很复杂,不是因为框架作者喜欢复杂,而是这个问题本来就复杂。
Dioxus 也必须面对这些问题。
五、Dioxus 的 counter 示例
文章用一个最简单的 Dioxus counter 组件作为例子:
#[component]
fn Counter() -> Element {
let mut x = use_signal(|| 0_u64);
let inc = move |_| x += 1;
let dec = move |_| x -= 1;
rsx! {
"{x}"
button { onclick: inc, "+" }
button { onclick: dec, "-" }
}
}
这个组件做了几件事。
第一,用 #[component] 标记一个组件。
第二,用 use_signal 创建响应式状态 x,初始值为 0。
第三,定义两个事件处理器:inc 和 dec。
第四,用 rsx! 宏写出 UI 结构。它看起来有点像 JSX,但在 Rust 里。
如果服务端渲染这个组件,服务端知道 x 初始是 0,也知道 UI 里有两个按钮。它可以输出类似这样的 HTML:
<!--node-id0 -->0<!--#-->
<button data-node-hydration="1,click:1">+</button>
<button data-node-hydration="2,click:1">-</button>
重点是:按钮上没有真正的 onclick。因为服务端不能把 Rust closure 直接放进 HTML。它只能输出 hydration hint,比如某个节点对应哪个事件。客户端之后拿到同样的状态,重新执行 render,建立映射,再把事件处理器装上去。
这就是 hydration 的本质:服务器先给你可见 HTML,客户端再补上交互能力。
六、如果用户在 hydration 中途点击按钮怎么办
这里有一个很实际的问题:如果服务器 HTML 已经显示出来,但客户端 JavaScript/WASM 还没加载完,或者 hydration 还没完成,此时用户点击按钮,会发生什么?
理论上,服务器输出的 HTML 可以包含真正的链接和表单,让用户即使在 hydration 之前也能通过浏览器原生行为完成操作。比如按钮可以是一个表单提交,链接可以是真正的 URL。这样即使 JavaScript 没加载完,页面也能工作。
但现实是,现代前端很少认真做这件事。很多应用默认交互依赖客户端 JS。一旦 hydration 没完成,按钮看起来能点,实际上不工作。用户可能以为页面卡住了。
Dioxus 也处在这个复杂空间里。它要处理 SSR、hydration、事件映射、客户端接管、数据一致性等问题。Rust 类型系统能帮很多忙,但这里有不少问题不是类型系统能直接解决的。
比如:hooks 的调用顺序是否一致?服务端和客户端是否渲染出同样结构?某个异步数据是否在服务端已经加载,而客户端又重新加载?这些都涉及运行时行为。
这也是作者后面说“我们离 Rust 里那种 if it compiles it works 还有点远”的原因。
七、异步数据加载:全栈框架永远绕不开的问题
Counter 示例很简单,因为状态初始值就是 0,没有数据库,没有网络请求,没有权限判断,没有缓存,没有错误处理。
真实应用不会这么简单。真实应用需要:
从数据库查询数据
请求外部 API
处理 loading 状态
处理错误状态
缓存结果
在服务端加载一部分数据
在客户端继续加载另一部分数据
避免重复请求
避免请求瀑布
避免 hydration mismatch
这就是 fullstack 框架最难的部分。
Dioxus 为这些问题提供了很多 hooks。文章列出了一大串:
try_use_context
use_after_suspense_resolved
use_callback
use_context
use_context_provider
use_coroutine
use_coroutine_handle
use_effect
use_future
use_hook
use_hook_did_run
use_hook_with_cleanup
use_route
use_router
use_navigator
use_memo
use_on_unmount
use_reactive
use_resource
use_root_context
use_set_compare
use_set_compare_equal
use_signal
use_signal_sync
use_reactive!
use_server_future
use_server_cached
use_drop
use_before_render
use_after_render
这份列表让人有点害怕。同步 hook、异步 hook、响应式 hook、缓存 hook、server-only hook、client-only hook,全都有。对熟悉 React 或 Solid 的人来说,这种模式不陌生;但对 Rust 开发者来说,它会显得很“不 Rust”。
因为 Rust 给人的习惯是:如果类型对了、借用对了、所有权对了,程序通常就已经过掉一大类错误。但 hooks 系统有自己的运行时规则。比如 hook 调用顺序不能变,不能在条件分支里随便调用 hook,不能在循环里改变 hook 数量。这些规则往往不是类型系统能完全表达的。
违反 hooks 规则时,你不一定得到编译错误,甚至不一定得到明确运行时错误。可能只是行为很奇怪,很难调试。
这不是 Dioxus 独有的问题。React 也有 hooks 规则,也需要 lint 来辅助。Dioxus 的困难是:Rust 用户可能期待更强的静态保证,但 fullstack UI 的复杂性并不会因为语言换成 Rust 就自动消失。
八、作者原本想狠狠吐槽 Dioxus
文章进入 “Love-hate” 部分后,作者坦白:他原本准备对 Dioxus 严厉一点。
原因是他曾经挑战自己,用 Dioxus 写了 Eurorust Paris 上使用的 quiz 软件。那次体验相当挫败。他本来想拿这次经历当材料,吐槽 Dioxus 的开发体验。
但深入研究后,他发现很多抱怨并不完全公平。
有些是自己误解了 Dioxus 的使用方式。
有些问题已经在主分支里修掉了。
有些不是 Dioxus 团队造成的,而是 Rust/WASM 前端生态目前的整体限制。
这点很重要。技术评价里很容易出现一种模式:第一次体验不顺,就得出“这个东西很烂”的结论。作者一开始也有这种倾向。但真正拆开看后,他对 Dioxus 的评价变得复杂了:它有很多不舒服的地方,但团队在做难事,也在快速修正问题。
九、dx 工具:Rust 前端需要自己的开发体验
Dioxus 提供 dx 工具,它包装了 cargo,负责把 Rust 编译到 WebAssembly,处理开发服务器、刷新、构建等工作。
这对 Rust 前端非常重要。因为如果每次都要手动跑 cargo build --target wasm32-unknown-unknown,再用 wasm-bindgen,再处理静态资源,再启动服务器,那开发体验会非常差。
dx serve 的目标就是让它更像现代前端工具:启动服务,修改代码,浏览器里看到结果。构建过程中浏览器还会显示加载界面,告诉你正在编译。
这听起来像很小的事,但对开发体验影响很大。前端开发者已经习惯 Vite、SvelteKit、Next.js、React Fast Refresh 那样的体验:保存文件,浏览器很快更新。Rust 编译到 WASM 的路径天然更重,所以工具链必须把这部分处理好。
Dioxus 在这里做了不少工作。作者原本准备夸 dx,这一点仍然成立。
但随之而来的是:如果应用 panic 怎么办?
十、panic 体验:应用卡死不能算好反馈
作者本来想吐槽一个问题:如果 Dioxus app panic,浏览器里的应用可能只是变得没有响应,看不到明显错误提示。对开发者来说,这很糟糕。你点按钮,没反应;页面不动;控制台也不一定给你一个友好的错误。
这种体验很挫败。尤其是你在写 Rust,心理预期是错误应该清楚一点,至少 panic 信息应该能看到。
但作者后来发现,主分支里已经有改进。Dioxus 团队显然也遇到了这个问题,并且已经加了错误显示。作者的原始吐槽因此失效了。
这段很能说明开源项目评价的困难。你写文章时基于某个版本体验;但框架可能在主分支里已经修了。特别是 Dioxus 这种快速迭代项目,抱怨一个问题时要留意它是否已经在路上。
十一、WASM stack trace:从不可读到勉强可调试
另一个原本想吐槽的点是 stack trace。
WASM 报错时,浏览器控制台里可能出现一堆函数名和十六进制 offset,比如 $func1234 之类。对 Rust 开发者来说,这几乎没法读。你不知道是哪一行代码,也不知道调用路径,只看到一个大 WASM 文件里的偏移。
后来作者发现了一个 Chrome 扩展:C/C++ DevTools Support (DWARF)。
名字听起来很像某种专门针对他的恶意软件,但它确实有用。它能读取 DWARF 调试信息,让 DevTools 显示 Rust 源文件和行号。虽然不一定能显示真正的函数名,但能看到源码位置,能点击跳转,能打断点,能 step in、step over、step out。
这比作者预期好很多。他甚至没意识到 WASM 调试已经走到这个程度,也没意识到生态已经选用 DWARF 来承载调试信息。
这部分很重要。很多人对 WASM 的印象还停留在“黑盒二进制,调试很痛苦”。确实仍然痛苦,但工具已经在进步。Rust 前端要想变得可用,调试体验必须接近 JavaScript 前端。否则一旦出 bug,开发者会立刻怀念 TypeScript 和浏览器原生 DevTools。
十二、Subsecond:hot patching 的诱惑和现实
接下来是 Dioxus 的 Subsecond,也就是 hot patching。
现代前端开发里,hot reload / fast refresh 已经是基本要求。你修改某个组件文件,保存,浏览器不应该整页刷新,也不应该把你带回首页。理想情况是,当前页面状态保留,只替换变化的组件。这样你可以在深层页面上调 UI,不用每次重新导航、重新输入、重新构造状态。
作者一开始以为自己已经在用 Dioxus 的 hot patching,于是觉得体验很糟:状态会丢,不是真的一秒内完成,也不够稳定。
后来才发现,他根本没启用。要传:
--hot-patch
启用后,体验明显好很多。修改代码后,浏览器里能很快看到结果,而且尽量保留应用状态。
当然,它仍然会崩。作者说它 crashes all the time。原因也能理解:Rust/WASM hot patching 比 JavaScript 框架的热更新复杂得多。JavaScript 本来就是动态加载和替换模块的世界;Rust 编译成 WASM 后,要在不刷新页面的情况下替换组件逻辑,难度更高。
但 promise 是存在的。作者看到这个功能后,承认 Dioxus 的方向很有吸引力。它还很早期,但如果能稳定下来,Rust 前端开发体验会明显改善。
有趣的是,启用 hot patching 后,stack trace 反而能显示 Rust 函数的 mangled name,但它又会破坏 DWARF 调试。于是开发者面临一个很现实的选择:你要热更新,还是要更好的调试?目前还不能两者兼得。
这就是早期生态的典型状态:每个功能都在进步,但组合起来还会互相踩。
十三、Dioxus 到底 spark joy 吗
文章最后正式回答标题问题:
Dioxus 能让人快乐吗?
作者的答案是:还没有。
至少在当时,不启用 Subsecond 等能力时,相比 Svelte 5 这类成熟前端方案,Dioxus 的体验仍然很不舒服。Svelte 5 是作者的黄金标准。和它相比,Dioxus 仍然有很多差距:构建慢、调试麻烦、生态不成熟、hooks 心智负担重、hot patching 还早期、WASM 工具链限制明显。
但这不是一个负面结论。
作者一开始有点怀疑 Dioxus。他以为自己会得到 Rust enum 这样的好处,但其他部分都会很糟。后来发现不是这样。
Dioxus 有一些真正有意思的设计。
比如 generational references 让事件处理器没那么痛苦。Rust 里写 UI 事件 handler 很容易撞上生命周期和借用问题,但 Dioxus 的设计在一定程度上缓解了这种痛苦。
比如 server-side functions 通过 WebSocket 工作得不错,并且能减少样板代码。全栈框架最吸引人的地方之一,就是可以用一种相对统一的方式调用服务端逻辑,而不是手写一堆 API client/server glue。
再比如 Dioxus 团队在做很多非常难的事情。他们有自己的 Flexbox 实现,并且和 Servo 分享;他们还在做自己的 HTML/CSS renderer,希望做桌面应用时不需要完整 Web 引擎。这些都不是简单包装现有浏览器能力,而是在更底层推进 Rust UI 生态。
所以结论是:
现在还不够快乐,但值得期待。
作者说,在 Rust/WASM 前端生态赶上 JavaScript 方案的开发体验之前,他仍然会继续在后端写 Rust,在前端写 TypeScript。
但后记又反转了一下:自从演讲之后,他又为一个即将发布的项目写了几千行 Dioxus。也就是说,虽然他仍然纠结,但已经投入进去了。
这非常真实。很多工具不是一用就爱上,也不是一用就放弃。它们处在那种“我明明有很多抱怨,但又忍不住继续写”的状态。Dioxus 对作者来说就是这样。
十四、这篇文章真正想讲什么
这篇文章表面上是在评价 Dioxus,但更深层是在讲 Rust 前端的现实处境。
Rust 开发者对工具经常有很高期待。我们习惯了编译器帮我们抓错,习惯了类型系统排除很多无意义状态,习惯了 “if it compiles, it probably works” 这种安全感。哪怕这句话并不绝对,它也代表一种开发体验。
但 Web 前端不是这样。
Web 前端充满运行时状态:DOM、事件、hydration、浏览器差异、CSS、异步数据、网络延迟、用户在加载过程中的操作、SEO、可访问性、history、scroll、focus、表单行为。框架能抽象很多东西,但不能把复杂性消灭。
Dioxus 把 Rust 带进这个世界,于是 Rust 开发者会发现:类型系统仍然有用,但它不能解决所有前端问题。hooks 规则仍然可能违反;hydration 仍然可能 mismatch;WASM 调试仍然有工具链问题;hot patching 仍然可能崩;client/server 边界仍然复杂。
所以评价 Dioxus 不能只问“它是不是 Rust”。Rust enum 很好,Rust 类型系统很好,但前端开发体验还需要 dev server、hot reload、错误 overlay、source map / DWARF 调试、资源处理、生态组件、CSS 方案、部署方式、服务端函数、数据加载约定。Dioxus 要补的是整个世界,不只是一个语法层。
从这个角度看,作者对 Dioxus 的态度其实很公平:现在不够快乐,但团队在做正确且困难的事情。
十五、对 Rust 开发者的启发
如果你是 Rust 后端或系统开发者,想尝试 Dioxus,这篇文章有几个提醒。
第一,不要带着纯 Rust 后端的预期进入前端。前端有大量运行时问题,很多不是编译器能完全解决的。你会遇到 hydration、hooks、WASM 调试、浏览器差异、CSS、event handler、热更新等问题。
第二,Dioxus 的 fullstack 能力很诱人,但也意味着复杂度更高。服务端渲染、客户端 hydration、server function、缓存、异步数据加载,每一项都值得认真理解。
第三,dx 是重要入口。Rust 前端开发体验很依赖工具链。如果工具链不顺手,再好的框架设计也会被编译等待、调试困难和刷新问题磨掉耐心。
第四,启用 hot patching 前后体验差距很大。不要像作者一样以为自己在用 hot patching,结果忘了加参数。
第五,WASM 调试已经比想象中更好,但仍然不完美。DWARF DevTools 支持能给源码行号和断点能力,但它和 hot patching 之间可能有冲突。
第六,和 Svelte、React、Vue 这类成熟前端生态相比,Dioxus 仍然年轻。选它意味着你愿意承受早期生态的不稳定,也愿意押注 Rust/WASM 前端未来。
十六、对前端开发者的启发
如果你是前端开发者,这篇文章也值得看。因为它从 Rust 视角重新解释了你可能早已习惯的东西。
比如 SSR + hydration。很多前端开发者已经把它当作理所当然,但从外部看,它其实非常复杂。服务器渲染一遍,客户端再渲染一遍,然后还要保证两边结构一致,这本来就是一个容易出错的系统。
比如 hooks。React 开发者知道 hooks 有规则,知道 lint 很重要。但 Rust 开发者会天然期待编译器能抓住更多东西。当这些规则不能被类型系统完全表达时,就会产生落差。
比如 hot reload。前端生态习惯了保存文件立刻更新,但对 Rust/WASM 来说,这是非常高难度的工程。Dioxus 的 Subsecond 即使还不稳定,也说明团队在认真追赶前端世界的开发体验标准。
所以这篇文章也提醒前端开发者:你们已经习惯的工具体验,其实是多年工程积累的结果。Rust 前端要追上它,并不容易。
十七、为什么作者最终还是写了几千行 Dioxus
最有意思的是后记。
演讲时,作者的结论是:Dioxus 还没有 spark joy;短期内他会继续后端 Rust、前端 TypeScript。
但演讲之后,他又为一个即将到来的项目写了几千行 Dioxus。于是前面的结论变得有点不完全真实。
这很像真实技术选择。我们很少因为一个工具完美才使用它。更多时候,是因为它有某些让人放不下的优点,即使同时有很多毛刺。
Dioxus 的吸引力可能就在这里:
Rust 类型系统
一套语言贯穿前后端
server functions 减少样板
generational references 缓解事件处理痛苦
WASM 前端的长期潜力
桌面端不依赖完整 Web engine 的野心
团队执行力强
这些东西足够让一个人一边吐槽,一边继续写。
这也是 Dioxus 当前最真实的位置:还不是 Svelte 5 那种“写起来就是爽”的状态,但已经有足够多让人愿意继续投入的东西。
十八、总结
这篇文章以一个问题开场:Dioxus 能让人快乐吗?Dioxus 是 Rust 生态里的 UI / fullstack 框架,目标是用一套 Rust 代码覆盖 Web、移动端、桌面端,甚至前后端。作者先用玩笑介绍 Dioxus 的名字、YC startup 背景和 Rust 项目生态梗,然后进入正题:要评价 Dioxus,必须先理解 Web 应用这些年为什么会演化到 SSR、hydration 和 fullstack 框架。
文章回顾了三代 Web 应用。第一代是服务器生成 HTML,浏览器只显示;第二代是 SPA,服务器提供 JSON/XML,客户端负责渲染;第三代是服务端渲染加客户端 hydration:服务器先输出 HTML,让用户尽快看到内容,客户端再加载同样数据、重新渲染、建立映射、安装事件处理器,让页面变成交互式。这个过程听起来合理,但包含很多难点:hydration 过程中用户点击怎么办?客户端和服务端渲染不一致怎么办?异步数据要怎么加载?组件之间如何避免请求瀑布?这些都是 fullstack 框架必须处理的复杂问题。
然后文章用 Dioxus counter 示例说明其基本写法:#[component] 定义组件,use_signal 管理状态,rsx! 描述 UI。服务端渲染时,Dioxus 输出 HTML 和 hydration hints,而不是直接输出真正事件处理器。客户端拿到同样数据后重新渲染,和服务器 HTML 建立映射,再安装事件处理。这个例子很好地展示了 Dioxus 的运行模型,也说明 fullstack UI 为什么复杂。
接着,文章讨论 Dioxus 的 hooks。Dioxus 提供大量 hooks:同步、异步、响应式、缓存、server-only、client-only,应有尽有。这说明框架试图覆盖很多实际问题,但也让人感到 intimidated。对 Rust 用户来说,最大落差是:hooks 规则不一定能被编译器完全检查。违反规则时,可能没有编译错误,甚至没有明确运行时错误,只是行为奇怪。这和许多 Rust 开发者习惯的 “if it compiles it works” 感受不同。
后半部分进入作者的实际体验。他原本准备严厉吐槽 Dioxus,因为用它写 Eurorust Paris 的 quiz 软件时体验很挫败。但深入研究后,他发现很多抱怨是误解、生态限制,或者主分支已经在修。dx 工具包装 cargo、处理 WASM 编译和开发服务器,是很重要的开发体验基础。panic 无提示卡死的问题在主分支已有改进。WASM stack trace 原本很难读,但 Chrome 的 DWARF DevTools 支持能显示 Rust 源码行号、跳转文件、打断点和单步调试。Subsecond hot patching 一开始被作者误判,因为他根本没启用;启用 --hot-patch 后,热更新体验明显变好,虽然仍然容易崩,而且会和 DWARF 调试产生冲突。
最终,作者回答标题问题:Dioxus 现在还没有 spark joy。和 Svelte 5 这样的成熟前端体验相比,它仍然不够舒服。但这不是简单否定。Dioxus 的 generational references 让事件处理器没那么痛苦,server-side functions 通过 WebSocket 工作得不错并减少样板代码。团队还在做 Flexbox 实现、和 Servo 共享布局工作、尝试自己做 HTML/CSS renderer,以便构建不依赖完整 Web engine 的桌面应用。这些都是困难且有价值的工作。
文章最后的后记很真实:演讲结束后,作者又为一个即将发布的项目写了几千行 Dioxus,所以“短期内继续 Rust 后端、TypeScript 前端”的结论有点被自己推翻。他仍然纠结,但也已经投入进去。
这篇文章真正传达的是:Dioxus 还不够成熟,还没有达到现代 JavaScript 前端框架那种丝滑开发体验;但它正在认真解决 Rust 前端必须面对的硬问题。Rust/WASM 前端生态要追上 Svelte、React、Vue 这些成熟方案,需要的不只是语言优势,还需要工具链、调试、热更新、数据加载、hydration、错误反馈、组件生态一起成熟。Dioxus 现在还不完全 spark joy,但已经让人愿意继续看下去。

387

被折叠的 条评论
为什么被折叠?



