Rust in Everything
基于 dioxus 0.7.10

dioxus 源码解析

全卷只对着一个版本写:

项版本tagcommit日期
dioxus 工作区(dioxus、dioxus-core、dioxus-rsx、dioxus-signals、dioxus-web、dioxus-ssr、dioxus-fullstack、dioxus-server、dioxus-router 等,同版本发布)0.7.10v0.7.1057d67942026-07-30
本站 Cargo.lock 里的 dioxus0.7.9v0.7.93e43ffa2026-05-07
本站用的 dx CLI0.7.9同上

本站跑的是 0.7.9,但 git diff v0.7.9 v0.7.10 只改了 packages/cli/ 下的 4 个文件,外加根 Cargo.toml 和 Cargo.lock 里的版本号。所以本卷讲的运行时代码(core、signals、web、ssr、fullstack、router)和本站在跑的逐字相同,「在本站中」一章不需要做升级评估。

dioxus 给自己的描述是「Build for web, desktop, and mobile, and more with a single codebase」。本卷只讲其中的 web 这一块:浏览器里的 wasm 客户端、服务端渲染(SSR)、fullstack(server function、水合、流式渲染)和 router。desktop、mobile、native(blitz)、liveview 只在源码地图里一句话带过;dx CLI 和热重载只讲它们怎么接进运行时,不展开 CLI 本身的源码。

全卷沿三条线展开:

  • 更新主线(一次点击):浏览器的 click 冒泡到根节点上唯一的监听器,wasm 里的闭包从目标往上找 data-dioxus-id,调 Runtime::handle_event;用户闭包里 count += 1 写 signal,WriteLock 释放时通知订阅者,组件的 ReactiveContext 发出 SchedulerMsg::Immediate;render_immediate 按高度重跑脏组件,同一个模板只比动态部分,得到一条 SetText;WebsysDom 把指令攒进 sledgehammer 缓冲,flush_edits 一次交给 JS;effect 最后跑。
  • 首屏主线(一次页面请求):GET / 落到 axum 的 fallback,SsrRendererPool 在一个本地线程上为这个请求新建一个 VirtualDom,rebuild_in_place 后等 suspense(use_server_future 在服务端跑完),用 dioxus_ssr 带水合标记渲染成 HTML,把 HydrationContext 序列化成 window.initial_dioxus_hydration_data;浏览器加载 wasm 后在同一份数据里 rebuild(不发 DOM 指令),再 rehydrate 认领服务端送来的节点。
  • 调用主线(一次 server function):#[post("/api/double")] 在同一个函数签名上生成两半:服务端那一半是一个 axum handler,用 inventory 登记,启动时挂到路由上;客户端那一半把参数编码成请求发出去,再把响应解码成 Result<T, ServerFnError>。两半用用户 crate 自己的 server feature 切开。

三条线每一步对应的源码和页面,见怎么读这一卷。

flowchart TB
  intro[00 导读] --> overview[01 全景]
  overview --> rsx["02 rsx 与组件"]
  rsx --> core["03 VirtualDom 内核"]
  core --> signals[04 响应式]
  core --> web[05 浏览器渲染]
  signals --> web
  core --> fs["06 SSR 与 fullstack"]
  web --> fs
  fs --> router[07 router]
  router --> design[08 设计取舍]
  design --> pit[09 常见坑]
  pit --> site[10 在本站中]
  site --> hands[11 动手]

实验环境

关于行为的说法(发出了哪些 Mutations、组件重跑了几次、服务端返回了什么 HTML、报什么错、rustc 说了什么、宏展开成什么)都配一个小程序和它的真实输出,平台是 M2 Max、macOS 27、rustc 1.99.0、Chrome 156(headless)。不需要浏览器的实验直接在原生程序里跑 VirtualDom(rebuild_to_vec、render_immediate_to_vec、dioxus_ssr::render);fullstack 实验用 dx build --platform web --fullstack 编出服务端和 wasm 两份产物,curl 看服务端 HTML,headless Chrome 看水合之后的 DOM。实验依赖锁定 dioxus = "=0.7.10",同一份锁文件里的 axum 是 0.8.9、tokio 是 1.54.1,和 axum 卷、tokio 卷对的是同一个版本。

各部分

  1. 00 导读:锁定的版本和本站的关系,三条主线每一步落在哪个函数、哪一页讲;同一份代码编两次的 feature 与 cfg,debug_assertions 下多出来的东西;工作区的 57 个 package 按层分组。
  2. 01 全景:dioxus 和 React、Leptos、Sycamore、Yew 在设计上的分界;一次点击、一次页面请求各从头到尾走一遍。
  3. 02 rsx 与组件:rsx! 怎么把静态部分切成编译期的 Template,VNode 和 DynamicNode,#[component] 和 Props。
  4. 03 VirtualDom 内核:本卷重点。VirtualDom 与 Runtime,scope 与 hooks,diff 与带 key 的列表,Mutations,调度,事件,suspense 与错误边界。
  5. 04 响应式:generational-box 让 Signal 能 Copy,ReactiveContext 的订阅与标脏,memo 与 effect,resource 与任务,context 与全局 signal,0.7 新增的 Store。
  6. 05 浏览器渲染:WebsysDom 与 sledgehammer 解释器,事件委托,document 与 eval。
  7. 06 SSR 与 fullstack:dioxus-ssr,一次请求的渲染,水合,server function,流式渲染,增量渲染与配置。
  8. 07 router:#[derive(Routable)] 生成什么,Router、Outlet、Link 的运行时。
  9. 08 设计取舍:把各章的取舍汇总到一起,每条链回有证据的页面。
  10. 09 常见坑:用报错原文或现象做标题,每个都先跑出最小复现。
  11. 10 在本站中:本站怎么起服务、在 dioxus 的路由前后挂了哪些中间件,页面怎么取数据,server function 的错误外泄。
  12. 11 动手:不依赖 dioxus,写一个迷你的「模板 + 虚拟 DOM + signal」,输出和 dioxus 同形的指令列表。

适合谁读

  • 用 dioxus 写过页面,想知道 use_server_future 和 use_resource 差在哪、为什么 hook 不能放进 if、server function 为什么能被直接 curl 的读者:从导读开始,按阅读路线里「只想会用」那条读。
  • 想读懂一个虚拟 DOM 框架怎么实现的读者:按顺序读。03 是全卷的重点,三条主线都要穿过 VirtualDom。
  • 需要的前置知识是 React 式的组件和 hooks 概念、async/await,服务端部分还需要 axum 卷的 Router 和 handler:dioxus 的服务端就是一个 axum::Router。

💬 关联讨论

发起讨论
加载中...