8.1 生命周期总览
> **本节目标**:了解 RML 视图和组件的生命周期阶段,以及各阶段的回调机制。
8.1.1 生命周期阶段
RML 视图和组件的生命周期分为四个阶段:
┌─────────────────────────────────────────────────────────────┐
│ 生命周期阶段 │
│ │
│ 1. 创建(Creation) │
│ └─ 结构体构造,字段初始化 │
│ │
│ 2. 加载(Loading) │
│ └─ 视图首次渲染完成,可访问 DOM │
│ │
│ 3. 更新(Updating) │
│ └─ 状态变化触发重新渲染 │
│ │
│ 4. 卸载(Unmounting) │
│ └─ 视图被销毁,释放资源 │
│ │
└─────────────────────────────────────────────────────────────┘
8.1.2 生命周期回调
RML 提供两个生命周期回调:
| 回调 | 触发时机 | 用途 | | ----------------- | -------------- | ------------------- | | `#[on_loaded]` | 视图首次渲染完成后 | 初始化数据、启动定时器、获取焦点 | | `#[on_unloaded]` | 视图被销毁前 | 清理资源、取消任务、保存状态 |
回调签名
#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut Context<Self>) {
// 视图加载完成
}
#[on_unloaded]
pub fn on_unloaded(&mut self, cx: &mut Context<Self>) {
// 视图即将卸载
}
8.1.3 生命周期流程
用户打开视图
│
▼
┌─────────────────────────────────┐
│ 1. 创建阶段 │
│ - 调用 new() 构造函数 │
│ - 初始化字段 │
│ - 注册到 GPUI Entity 系统 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 2. 加载阶段 │
│ - 编译 .rml 模板 │
│ - 构建视图树 │
│ - 首次渲染 │
│ - 触发 #[on_loaded] │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 3. 更新阶段(可重复) │
│ - 状态变化(cx.notify()) │
│ - 重新渲染变化的部分 │
│ - 触发 #[on_prop_change] │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 4. 卸载阶段 │
│ - 触发 #[on_unloaded] │
│ - 清理资源 │
│ - 从视图树移除 │
│ - 销毁 Entity │
└─────────────────────────────────┘
8.1.4 创建阶段
创建阶段通过构造函数完成:
#[derive(IModel)]
#[component]
pub struct MyView {
pub data: Vec<Item>,
pub is_loading: bool,
// 私有字段(不参与数据绑定)
timer: Option<Timer>,
subscription: Option<Subscription>,
}
impl MyView {
pub fn new() -> Self {
Self {
data: Vec::new(),
is_loading: false,
timer: None,
subscription: None,
}
}
}
创建阶段的限制
- 不能访问 `cx`(上下文) - 不能启动异步任务 - 不能获取焦点 - 不能访问 DOM
这些操作需要在 `#[on_loaded]` 中完成。
8.1.5 加载阶段
加载阶段在视图首次渲染完成后触发 `#[on_loaded]`:
impl MyView {
#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut Context<Self>) {
// 1. 加载数据
self.load_data(cx);
// 2. 启动定时器
self.start_timer(cx);
// 3. 订阅全局状态
self.subscribe_to_global_state(cx);
// 4. 获取焦点
self.focus_input(cx);
}
fn load_data(&mut self, cx: &mut Context<Self>) {
self.is_loading = true;
cx.notify();
cx.spawn(|this, mut cx| async move {
let data = fetch_data().await;
let _ = this.update(&mut cx, |this, cx| {
this.data = data;
this.is_loading = false;
cx.notify();
});
}).detach();
}
fn start_timer(&mut self, cx: &mut Context<Self>) {
self.timer = Some(cx.spawn(|this, mut cx| async move {
loop {
cx.background_executor()
.timer(std::time::Duration::from_secs(1))
.await;
let _ = this.update(&mut cx, |this, cx| {
this.tick(cx);
});
}
}));
}
fn subscribe_to_global_state(&mut self, cx: &mut Context<Self>) {
self.subscription = Some(cx.subscribe(&self.global_state, |this, _, event, cx| {
this.handle_global_event(event, cx);
}));
}
fn focus_input(&mut self, cx: &mut Context<Self>) {
self.input_element.focus(cx);
}
}
加载阶段能做什么
- 访问 `cx`(上下文) - 启动异步任务 - 访问 DOM(通过 `ElementRef`) - 获取焦点 - 订阅事件 - 加载数据
8.1.6 更新阶段
更新阶段由 `cx.notify()` 触发:
#[command]
pub fn increment(&mut self, _: &ClickEvent, cx: &mut Context<Self>) {
self.count += 1; // 1. 修改状态
cx.notify(); // 2. 通知变化 → 3. 重新渲染
}
属性变化回调
组件可以通过 `#[on_prop_change]` 监听属性变化:
#[derive(IModel)]
#[component]
pub struct DataView {
pub data_id: u64,
pub data: Option<Data>,
}
impl DataView {
#[on_prop_change(data_id)]
pub fn on_data_id_change(&mut self, cx: &mut Context<Self>) {
self.load_data(self.data_id, cx);
}
}
8.1.7 卸载阶段
卸载阶段在视图被销毁前触发 `#[on_unloaded]`:
impl MyView {
#[on_unloaded]
pub fn on_unloaded(&mut self, _cx: &mut Context<Self>) {
// 1. 取消定时器
if let Some(timer) = self.timer.take() {
timer.abort();
}
// 2. 取消订阅
if let Some(subscription) = self.subscription.take() {
subscription.unsubscribe();
}
// 3. 保存状态
self.save_state();
}
fn save_state(&self) {
// 保存到本地存储
}
}
卸载阶段必须做的事
- 取消异步任务 - 取消订阅 - 释放资源(文件句柄、网络连接) - 保存状态
8.1.8 生命周期的触发时机
视图的创建和卸载
// 创建视图
let view = cx.new_model(|_| MyView::new());
// 视图首次渲染 → 触发 #[on_loaded]
// ... 用户操作 ...
// 视图被销毁 → 触发 #[on_unloaded]
条件渲染的生命周期
<div if={is_visible}>
<MyView />
</div>
- `is_visible` 从 `false` 变 `true`:创建视图,触发 `#[on_loaded]` - `is_visible` 从 `true` 变 `false`:销毁视图,触发 `#[on_unloaded]`
列表渲染的生命周期
<ul>
<li each={item in items} key={item.id}>
<MyView data={item} />
</li>
</ul>
- 新增项:创建新视图,触发 `#[on_loaded]` - 删除项:销毁视图,触发 `#[on_unloaded]` - 移动项:不触发(通过 `key` 复用)
8.1.9 生命周期的注意事项
1. `#[on_loaded]` 只触发一次
#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut Context<Self>) {
println!("视图加载完成"); // 只打印一次
}
2. `#[on_unloaded]` 在销毁前触发
#[on_unloaded]
pub fn on_unloaded(&mut self, cx: &mut Context<Self>) {
// 此时还能访问 cx,可以做清理工作
println!("视图即将卸载");
}
3. 异步任务必须在卸载时取消
#[on_unloaded]
pub fn on_unloaded(&mut self, _cx: &mut Context<Self>) {
// 不取消会导致任务继续运行,访问已销毁的视图
if let Some(task) = self.async_task.take() {
task.abort();
}
}
4. 避免在 `#[on_loaded]` 中做重计算
// ❌ 阻塞 UI
#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut Context<Self>) {
let data = heavy_computation(); // 阻塞
self.data = data;
cx.notify();
}
// ✅ 异步执行
#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut Context<Self>) {
cx.spawn(|this, mut cx| async move {
let data = heavy_computation().await; // 不阻塞
let _ = this.update(&mut cx, |this, cx| {
this.data = data;
cx.notify();
});
}).detach();
}
8.1.10 生命周期与状态管理
状态持久化
#[derive(IModel)]
#[component]
pub struct EditorView {
pub content: SharedString,
}
impl EditorView {
pub fn new() -> Self {
Self {
content: SharedString::default(),
}
}
#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut Context<Self>) {
// 从本地存储加载内容
if let Some(saved) = cx.local_storage().get("editor_content") {
self.content = saved.into();
cx.notify();
}
}
#[on_unloaded]
pub fn on_unloaded(&mut self, cx: &mut Context<Self>) {
// 保存内容到本地存储
cx.local_storage().set("editor_content", &self.content);
}
}
跨视图状态同步
#[derive(IModel)]
#[component]
pub struct ParentView {
pub shared_state: Entity<SharedState>,
pub child_view: Entity<ChildView>,
}
impl ParentView {
#[on_loaded]
pub fn on_loaded(&mut self, cx: &mut Context<Self>) {
// 订阅共享状态
cx.subscribe(&self.shared_state, |this, state, event, cx| {
// 同步状态到子视图
this.child_view.update(cx, |child, cx| {
child.sync_state(event, cx);
});
}).detach();
}
}
8.1.11 小结
RML 的生命周期管理:
- **四个阶段**:创建 → 加载 → 更新 → 卸载 - **两个回调**:`#[on_loaded]`、`#[on_unloaded]` - **创建阶段**:构造函数初始化字段 - **加载阶段**:首次渲染完成,可访问 DOM 和 cx - **更新阶段**:`cx.notify()` 触发重新渲染 - **卸载阶段**:清理资源,取消任务
下一节 → [8.2 on_loaded 与初始化](./on-loaded.md)