生命周期与 Runtime 循环
很多人第一次接触 Ansiq,会把下面两件事混成一件事:
- app 生命周期
- runtime 的每次更新循环
它们有关,但不是同一个层次。
先看 app 生命周期
一个 Ansiq app 在高层上通常会经历:
- 创建 app 实例
- 进入 terminal session
- 调用
mount(...) - 做第一次
render(...) - 进入 runtime loop
- 在输入、消息、响应式变化下反复
update(...)/ rerender - 退出并恢复终端状态
1create app
2enter terminal session
3mount(...)
4first render(...)
5runtime loop
6exit and restore terminal
这条链回答的是:
一个 app 在一生中会经历哪些阶段?
再看 runtime loop
runtime loop 关注的是另一件事:
某一次变化发生后,系统如何把它稳定地落到终端上?
Ansiq 当前真正协调的是:
- 输入事件
- app 消息
- reactive flush
- rerender 决策
- partial relayout
- invalidated regions
- framebuffer diff
- terminal patch emission
1input / async messages / reactive flush
2dirty scope collection
3subtree rerender or full rerender
4partial relayout
5invalidated regions
6framebuffer diff
7terminal patch emission
App trait 的四个关键方法
pub trait App {
type Message: Send + 'static;
fn mount(&mut self, _handle: &RuntimeHandle<Self::Message>) {}
fn render(&mut self, cx: &mut ViewCtx<'_, Self::Message>) -> Element<Self::Message>;
fn update(&mut self, message: Self::Message, handle: &RuntimeHandle<Self::Message>);
fn on_unhandled_key(
&mut self,
_key: Key,
_handle: &RuntimeHandle<Self::Message>,
) -> bool {
false
}
}mount(...)
mount(...) 适合做“应用一启动就应该存在”的事情,例如:
- 启动后台采样
- 建立长连接
- 发出初始消息
它不负责画 UI。
render(...)
render(...) 负责描述当前 UI 树,并建立这一轮的响应式读取。
它不直接发 terminal patch,也不自己决定 relayout。
update(...)
update(...) 负责消费消息并修改 app 状态。
它回答的是:
- 用户做了什么
- 后台任务带回了什么新事实
- 这些事实怎样变成 app state
on_unhandled_key(...)
当 widget 没有消费某个按键、runtime 的全局焦点导航也没有消费时,按键会回到 app。
它适合:
- 全局快捷键
Escape取消- 模式切换
- 命令入口
fn on_unhandled_key(
&mut self,
key: Key,
handle: &RuntimeHandle<Self::Message>,
) -> bool {
if key == Key::Esc {
let _ = handle.emit(Message::Cancel);
return true;
}
false
}一条常见误解
很多框架把主循环写成“有事件就重画一次”。Ansiq 不够用这么简单的模型,因为它要同时协调:
- 输入
- 异步任务
- 局部 rerender
- 局部 relayout
- viewport / history
所以 runtime loop 在 Ansiq 里是架构边界,不是内部细节。
下一步
继续阅读 响应式 和 响应式系统原理。
前一页教你怎么用 signal / computed / effect,后一页解释它们为什么要和 UI tree 分层。
Last updated on