3
0

WinUI Rust 学习笔记-03 项目结构

2026-08-17
2026-08-17
文章摘要
|

目标:系统理解一个 Windows Reactor 项目的结构——每个文件、每个核心类型 分别做什么、负责什么,以及 windows-reactor 库内部是如何组织的。

1. 一个应用的两个层面

理解 Reactor 项目,可以把问题拆成两个层面:

┌─────────────────────────────────────────────────┐
│  你的代码层(声明式)                              │
│  - render 函数:状态 → Element                    │
│  - hooks:use_state / use_effect ...              │
│  - 组件函数:把 UI 拆成可复用单元                   │
├─────────────────────────────────────────────────┤
│  框架层(windows-reactor,命令式)                 │
│  - App / RenderCx / Element / Reconciler         │
│  - 把 Element 树 diff 后应用到真实 WinUI 控件树     │
│  - 管理 Windows 窗口、消息循环、运行时              │
├─────────────────────────────────────────────────┤
│  平台层(WinUI 3 / Windows App SDK)              │
│  - 真正的控件、布局、渲染、输入事件                 │
└─────────────────────────────────────────────────┘

你只需要关心最上面一层:写 render 函数。下面两层由框架处理。

2. 项目里的文件分工

2.1 Cargo.toml —— 依赖清单

负责声明「这个程序依赖什么」。关键点:

  • [dependencies] windows-reactor:UI 库(编译期 + 运行期)。

  • [build-dependencies] windows-reactor-setup:构建期工具,不进最终程序。

  • edition = "2024":必需,windows-reactor 用了 2024 edition 语法。

2.2 build.rs —— 构建钩子

Cargo 在编译前先编译并运行 build.rs。它的职责:

部署模型

职责

as_framework_dependent()

复制 bootstrap DLL 到输出目录,程序启动时用它引导已安装的运行时

as_self_contained()

下载完整运行时 + WebView2 相关文件打进输出目录,并嵌入清单

as_example()

同框架依赖,但输出到 target/examples/(官方示例用)

什么时候需要重新跑 build.rs? 切到别的目标架构(x86/arm64)时,构建脚本会自动重跑。

2.3 src/main.rs —— 应用逻辑

一个典型 Reactor 应用的 main.rs 结构(由简到繁):

#![windows_subsystem = "windows"]

use windows_reactor::*;

// ① 简单应用:一个 render 函数直接到底
fn app(cx: &mut RenderCx) -> Element {
    // 用 hooks 读状态
    let (name, set_name) = cx.use_state(String::new());
    // 用 builder 拼界面
    vstack((text_block(format!("Hello, {name}")), text_box().on_text_changed(...)))
        .spacing(8.0)
        .into()
}

fn main() -> Result<()> {
    bootstrap()?;                      // 初始化运行时
    App::new()
        .title("My App")
        .inner_size(800.0, 600.0)
        .backdrop(Backdrop::Mica)      // 亚克力/云母背景
        .render(app)
}

随着应用变大,推荐把 main.rs 拆成模块(见第 4 节「项目组织」)。

3. 核心类型与职责

3.1 RenderCx —— 渲染上下文

  • 是什么:render 函数唯一的参数,一次渲染会话的上下文。

  • 负责:提供全部 hooks(use_stateuse_refuse_effectuse_reduceruse_resourceuse_contextuse_open_window 等), 让你在 render 期间读取/写入应用状态。

  • 不负责:不直接操作控件。它只管理「状态与渲染」的关系。

3.2 Element —— 界面描述值

  • 是什么:一个不可变的、可复用的「界面描述」。它不是控件实例,而是描述。

  • 负责:携带「用什么控件、什么属性、什么子元素」的全部信息。

  • 怎么来:builder 调用 .into() 产生。

  • 怎么用:作为 render 函数的返回值、组件的返回值、容器的子元素。

一句话记忆:Element 是「图纸」,WinUI 控件是「房子」。 Reactor 拿到新图纸后,对比旧房子,只敲墙改门,而不是推倒重建。

3.3 App —— 应用外壳

  • 是什么:顶层应用构建器。

  • 负责:配置主窗口(标题、尺寸、背景、图标、presenter)、注册退出回调、 启动 WinUI 消息循环。

  • 入口.render(f).run(factory)

  • 生命周期render 是阻塞的——它启动消息循环,直到最后一个窗口关闭, 进程才退出(on_exit 回调在此刻、UI 线程上执行)。

3.4 bootstrap() —— 运行时初始化

  • 是什么windows-reactor 提供的顶层函数(仅框架依赖模式需要)。

  • 负责:初始化 Windows App SDK 运行时,让后续创建的 WinUI 控件可用。

  • 时机:必须在创建任何窗口/控件前调用一次。

3.5 组件(Component / 组件函数)

  • 是什么:可复用的「界面 + 逻辑」单元。

  • 组件函数:普通 Rust 函数,接受 &mut RenderCx,返回 Element。 Reactor 的 function_component 机制可给它加记忆化(memo)等能力。

  • Component trait:更底层的接口(render(&self, props, cx) -> Element), 供需要 props 的复杂组件使用。

4. 项目组织:从小到大的演进

4.1 单文件

所有逻辑都在 main.rs。适合学习期与小型工具。

4.2 按模块拆分(中大型推荐)

src/
├── main.rs            # App 配置 + bootstrap + 入口
├── app.rs             # 根 render 函数:组装整体布局
├── components/        # 自建组件
│   ├── mod.rs
│   ├── header.rs
│   └── todo_item.rs
├── state.rs           # 状态类型与 reducer
└── api.rs             # 网络/数据访问层

模块化原则:一个组件一个文件;状态与 UI 分离;数据访问独立

官方示例 crates/samples/reactor/gallery/ 是一个结构完整的多页面应用,值得研读:

gallery/
├── Cargo.toml
├── build.rs
└── src/
    ├── main.rs          # 入口
    ├── ...              # 页面组件、分类导航等

5. windows-reactor 库内部结构

源码位于 crates/libs/reactor/src/。不要求你读懂全部,但知道「谁负责什么」 能帮你更快定位问题:

模块

负责

widgets/

★ 全部 60 个控件的 builder 实现(每个控件一个文件)

hooks.rs

★ 全部 hooks 的实现(use_state、use_effect……)

element.rs

Element 类型与 ElementExt 样式扩展 trait

app.rs

AppReactorWindow、多窗口注册表、use_open_window

bootstrap.rs

bootstrap() 运行时初始化

reconciler/

核心 diff 算法:把新旧 Element 树对比、应用到真实控件树

backend/

与 WinUI 控件树打交道的底层(创建控件、设置属性、绑定事件)

style.rs

样式(Thickness、CornerRadius 等类型与辅助函数)

widget.rs

控件公共 trait Widgetlist_view/grid_view 等数据控件

generated.rs

从 winmd 元数据生成的控件绑定代码

host.rs

窗口宿主(ReactorHost),管理单个窗口的控件树

engine.rs

渲染引擎(调度 render、收集事件)

reference.rs

元素引用(ElementRef,操作已挂载的控件)

drag.rs / interaction.rs

拖拽、指针等交互辅助

fault.rs

错误处理与 panic 边界

6. 一句话总结每个部分

部分

一句话职责

Cargo.toml

声明依赖与元信息

build.rs

部署 Windows App SDK 运行时

render 函数

状态 → 界面(每次状态变化都会重跑)

RenderCx

提供 hooks,管理状态

Element

界面描述(图纸),经 .into() 获得

App

配置窗口并启动消息循环

bootstrap()

初始化运行时

组件函数

可复用的 UI + 逻辑单元

reconciler

把新图纸 diff 后应用到真实控件树

7. 设计原则速记

  1. UI 是状态的函数界面 = f(状态)。不手动增删控件。

  2. 状态只能通过 setter 改:改完自动重渲染。

  3. 组件可复用:把重复的界面块抽成组件函数。

  4. 样式通过 builder 链设置.font_size().margin()……(后面会说明)。

  5. 性能靠记忆化use_memomemo 组件避免无谓重渲染(后面会说明)。


小结:理解了项目结构、核心类型分工与库的内部组织。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论