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)