10.3 热重载

> **本节目标**:理解 RML 的热重载机制,掌握开发期的实时迭代工作流。

10.3.1 热重载的价值

传统 GPUI 开发中,每次改 UI 都要重新编译运行,耗时数十秒。RML 的热重载让 `.rml` 模板的修改在**秒级**内反映到运行中的窗口,极大缩短反馈循环。

| 改动类型 | 是否可热重载 | | ----------- | ---------------- | | `.rml` 模板 | ✅ 完全热重载 | | 样式表 | ✅ 完全热重载 | | ViewModel 字段 | ❌ 需重新编译 | | 命令 / 计算属性 | ❌ 需重新编译 | | Model 结构 | ❌ 需重新编译 |

热重载覆盖“UI 调试”这一最高频场景,业务逻辑变更仍需重新编译。

10.3.2 工作原理

┌──────────────────────────────────────────────────────────┐
│                    RML 热重载流程                            │
│                                                          │
│  文件监听器 ──▶ 增量解析 ──▶ 语义验证 ──▶ 重新生成渲染代码         │
│                                                  │       │
│                                                  ▼       │
│  运行时热重绘 ◀── 通过 IPC 通知应用 ◀── 输出到内存缓冲          │
│       │                                                  │
│       ▼                                                  │
│  替换当前视图的 Render 实现,保留 ViewModel 状态                │
└──────────────────────────────────────────────────────────┘

关键点:

1. **文件监听**:watch `src/**/*.rml` 与 `**/*.rmlcss` 2. **增量编译**:只重编译变化的文件,不重新跑全量 build 3. **状态保留**:ViewModel 状态不丢失,UI 平滑过渡 4. **IPC 通知**:编译进程通过本地 socket 通知运行中的应用

10.3.3 启用热重载

Cargo.toml

[dependencies]
rml = { version = "0.1", features = ["hot-reload"] }

[profile.dev]
# 热重载只在 dev profile 下启用

main.rs

fn main() {
    rml::init_hot_reload(); // 启动文件监听
    Application::new().run(|cx| {
        cx.open_view::<AppView>();
    });
}

运行

cargo run

应用启动后,修改任何 `.rml` 文件并保存,窗口立即更新。

10.3.4 热重载的边界

可热重载的改动

- 增删改元素 - 修改属性、class、style - 修改 `if` / `each` 条件 - 修改 `value={field}` 绑定路径(前提:路径在 ViewModel 中存在) - 修改插值表达式 - 修改样式表内容

不可热重载的改动

- ViewModel 新增 / 删除字段 → 重新编译 - 修改命令方法签名 → 重新编译 - 修改 Model 结构 → 重新编译 - 修改 `value={field}` 指向不存在的字段 → 热重载失败,提示错误

部分热重载失败的处理

当热重载遇到错误(如绑定路径不存在)时:

- 应用**不崩溃**,保持上一个有效状态 - 在窗口角落显示错误提示 - 终端打印错误详情 - 修复后保存,自动重试

10.3.5 热重载与状态保留

热重载的核心价值是**保留 ViewModel 状态**。例如调试一个表单时,已填写的字段在改模板后依然存在。

状态保留的机制

热重载时:

1. 旧 View 被销毁,但 ViewModel 的 `Entity` 被保留 2. 新 View 用同一 ViewModel 重新构建 3. 绑定重新建立,UI 反映当前状态

状态丢失的场景

- ViewModel 字段类型变化(如 `String` → `u32`):旧值无法迁移,重置为默认 - 重新编译运行:所有状态归零 - 视图被关闭重开:ViewModel 重建

10.3.6 热重载的最佳实践

1. 用 mock 数据填充状态

调试 UI 时,先在 `#[on_loaded]` 中填入典型数据,这样热重载后能立即看到有内容的界面:

#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut ViewContext<Self>) {
    #[cfg(debug_assertions)]
    {
        self.items = mock_items();
        self.user = mock_user();
        cx.notify();
    }
}

2. 频繁保存

热重载在保存时触发。养成“改一点存一下”的习惯,实时看效果。

3. 配合 Inspector

热重载 + Inspector 是最强调试组合:改模板 → 看 Inspector → 再改 → 再看。

4. 隔离复杂视图

把大视图拆成小组件,每个组件可独立热重载,避免一处改动触发全量重编译。

10.3.7 热重载的性能

| 文件规模 | 增量编译耗时 | | ----------- | --------- | | 单文件小改动 | < 100ms | | 单文件大改动 | 100-500ms | | 跨文件样式变更 | 200ms-1s | | 全量重编译(不可热重载) | 5-30s |

热重载比全量编译快 10-100 倍,是开发期的主要反馈手段。

10.3.8 排查热重载问题

热重载不生效

1. 检查 `rml` 是否启用 `hot-reload` feature 2. 检查是否在 `dev` profile 3. 检查文件是否在被监听的目录内 4. 检查 IDE 是否真的写盘(某些 IDE 缓存写入)

热重载后状态丢失

1. 检查是否触发了全量重编译(看终端日志) 2. 检查 ViewModel 字段类型是否变化 3. 检查视图是否被关闭重开

热重载报错

1. 看终端输出的错误详情 2. 用 `rml-expand` 看生成代码 3. 修复后保存,自动重试

下一节 → [10.4 构建流程](./build-pipeline.md)