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