3.1 绑定系统总览
> **本节目标**:建立对 RML 绑定系统的整体认知——绑定方向、绑定源、绑定路径、绑定引擎的角色分工。
3.1.1 绑定的四个维度
理解 RML 数据绑定,需要从四个维度入手:
维度一:绑定方向
| 方向 | 语法 | 数据流 | 适用场景 | | ------ | ------------------- | ---------------- | -------- | | 单向绑定 | `{field}` | ViewModel → View | 显示数据 | | 双向绑定 | `value={field}` / `checked={field}` | ViewModel ↔ View | 表单输入 | | 一次性绑定 | `<span once>{x}</span>` | ViewModel → View(仅首次) | 初始化快照 |
维度二:绑定源
绑定源是数据的提供者,在 RML 中始终是当前的 ViewModel:
#[derive(IModel)]
#[component]
pub struct MyView {
pub user_name: SharedString, // ← 绑定源
pub count: i32, // ← 绑定源
}
<!-- 绑定到 ViewModel 字段 -->
<p>{user_name}</p>
<p>{count}</p>
维度三:绑定路径
绑定路径是访问绑定源的方式,支持:
<!-- 直接字段 -->
<p>{user_name}</p>
<!-- 字段方法 -->
<p>{todos.len()}</p>
<!-- 嵌套字段 -->
<p>{user.profile.name}</p>
<!-- 列表项字段(在 each 内) -->
<li each={todo in todos}>{todo.text}</li>
<!-- 计算属性 -->
<p>{completed_count}</p>
维度四:绑定模式
| 模式 | 说明 | 触发更新的条件 | | ------ | ------------------- | ----------------- | | 默认模式 | ViewModel 字段变化时更新 | `cx.notify()` 调用后 | | 计算模式 | 依赖的字段变化时自动更新 | 依赖字段变化时 | | 一次性模式 | 仅首次渲染时计算 | 永不更新 |
3.1.2 绑定系统的角色
RML 绑定系统由三个角色协作:
┌──────────────┐ 编译期解析 ┌──────────────┐
│ .rml 文件 │ ─────────────▶ │ 绑定编译器 │
│ {field} │ │ (build.rs) │
└──────────────┘ └──────┬───────┘
│
▼ 生成
┌──────────────┐ 运行时订阅 ┌──────────────┐
│ ViewModel │ ◀───────────── │ 绑定运行时 │
│ (Entity) │ │ (RML Runtime)│
└──────────────┘ └──────┬───────┘
│ │
▼ cx.notify() ▼ 通知
┌──────────────┐ ┌──────────────┐
│ View 重绘 │ ◀───────────── │ GPUI 渲染 │
└──────────────┘ └──────────────┘
绑定编译器(编译期)
- 解析 `.rml` 中的 `{field}` 表达式 - 检查 `field` 是否存在于 ViewModel - 检查类型是否可渲染 - 生成订阅代码
绑定运行时(运行时)
- 维护 ViewModel 字段与 UI 元素的订阅关系 - 监听 `cx.notify()` 调用 - 触发受影响的 UI 元素重新渲染
ViewModel(数据源)
- 持有状态字段 - 在命令方法中修改字段后调用 `cx.notify()` - 计算属性通过 `#[computed]` 自动追踪依赖
3.1.3 绑定的生命周期
一个绑定从创建到销毁经历以下阶段:
1. 编译期:解析 {field} → 生成订阅代码
↓
2. 视图加载:创建订阅,关联 ViewModel 字段
↓
3. 首次渲染:读取字段值,渲染到 UI
↓
4. 字段变化:cx.notify() → 通知订阅者 → 重新渲染
↓
5. 视图卸载:取消订阅,释放资源
⚠️ **注意**:订阅的生命周期与视图绑定,视图卸载时自动清理,无需手动管理。详见 [第 8 章 · 订阅与取消](../08-lifecycle/subscriptions.md)。
3.1.4 绑定与 GPUI Entity 的关系
RML 的 ViewModel 是 GPUI 的 Entity。理解这一点对掌握绑定至关重要:
#[derive(IModel)] // ← 使结构体成为 GPUI Entity
#[component]
pub struct MyView {
pub count: i32,
}
- `#[derive(IModel)]` 让 `MyView` 成为 GPUI 管理的 Entity - 字段 `count` 自动成为响应式状态 - `cx.notify()` 通知 GPUI 该 Entity 已变化,需要重新渲染 - GPUI 调用 `Render::render`,RML 生成的代码重新读取绑定值
#[command]
pub fn increment(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
self.count += 1;
cx.notify(); // ← 通知 GPUI 重新渲染
}
3.1.5 绑定的性能特性
| 特性 | 说明 | | ----------- | --------------------------------- | | 编译期路径检查 | 绑定路径错误在编译期暴露,不会带到运行时 | | 零反射 | 生成的代码直接访问字段,无运行时反射开销 | | 细粒度订阅 | 只有依赖变化字段的 UI 元素重新渲染 | | 计算属性缓存 | `#[computed]` 结果被缓存,依赖未变化时不重新计算 | | 批量更新 | 多次 `cx.notify()` 在同一帧内合并为一次重绘 |
3.1.6 与 WPF 绑定的对比
| 维度 | WPF | **RML** | | -------- | -------------------- | -------------------- | | 绑定语法 | `{Binding FieldName}` | `{field}` | | 绑定方向 | `Mode=OneWay/TwoWay` | `value={field}` 自动推断双向 | | 计算属性 | 无原生支持,需写 Converter | `#[computed]` 一等公民 | | 值转换器 | `IValueConverter` | `Converter` trait | | 编译期检查 | 无(运行时反射) | **有(编译期路径检查)** | | 性能 | 运行时反射开销 | **零运行时开销** |
💡 **设计要点**:RML 借鉴 WPF 的绑定理念,但用编译期生成替代运行时反射,既保留了 WPF 的表达力,又获得了原生性能。
3.1.7 小结
RML 绑定系统的核心概念:
- **绑定方向**:单向、双向、一次性 - **绑定源**:ViewModel 的 `pub` 字段 - **绑定路径**:字段、方法、嵌套字段、计算属性 - **绑定引擎**:编译期解析 + 运行时订阅
后续章节会逐一深入每种绑定能力。
下一节 → [3.2 单向绑定](./one-way-binding.md)