Skip to Content
指南响应式

响应式

Ansiq 当前推荐的响应式原语是:

  • signal
  • computed
  • effect

如果你来自 React 背景,最值得先切换的心智是:

  • Ansiq 不以“整组件重跑”为核心
  • Ansiq 更接近 Vue / Solid 的细粒度响应式
  • 响应式系统负责“谁脏了”,runtime 负责“怎么更新终端”

继续沿用 NotesApp

第一个应用 里,我们已经有了一个最小可用的输入与提交应用。

现在我们不换例子,而是在它的基础上加两件事:

  1. 一个派生值:当前输入长度
  2. 一个副作用:输入清空时触发响应式 effect
fn render(&mut self, cx: &mut ViewCtx<'_, Self::Message>) -> Element<Self::Message> { let draft = cx.signal(|| String::new()); let current = draft.get(); let input_len = cx.computed({ let draft = draft.clone(); move || draft.get().chars().count() }); cx.effect({ let draft = draft.clone(); move || { if draft.get().is_empty() { // 这里用来示意 effect 的角色: // 做副作用,而不是直接生成 UI } } }); view! { <Paragraph text={"Type and press Enter"} /> <Input value={current.clone()} on_change={{ let draft = draft.clone(); move |next| draft.set_if_changed(next) }} on_submit={|next| Some(Message::Submit(next))} /> <Paragraph text={format!("Characters: {}", input_len.get())} /> <Paragraph text={format!("Last submit: {}", self.submitted)} /> } }

这时同一个小应用已经开始展示响应式分层:

  • draft 是基础状态
  • input_len 是派生状态
  • effect 是副作用边界

signal

signal 是最基础的可变响应式状态。

它适合:

  • 输入框值
  • 当前选中项
  • pane 展开/收起
  • 会被多个 UI 部分读取的小状态

NotesApp 里,draft 就是最典型的 signal

  • 它表示“当前正在编辑、但还没提交”的局部值
  • 它需要被 Input 和派生值同时读取
  • 它不需要立刻变成 app 的持久业务状态

computed

computed 表示派生值。

它适合:

  • 基于多个 signal 组合出的展示值
  • 已排序/已过滤的结果
  • 纯展示导向的派生状态

NotesApp 里,input_len 是一个很典型的 computed

  • 它依赖 draft
  • 它不该自己存一份副本
  • 它只是一个派生展示值

effect

effect 用来表达“依赖变化时发生的副作用”。

这里要特别强调:

effect 不同于 React 的 useEffect
它没有依赖数组,依赖由执行时的读取自动追踪。

它适合:

  • 响应式驱动的后台行为
  • 需要与 runtime、外部系统、任务协作的副作用
  • 不应直接编码进 UI 表达式的逻辑

一个很好用的区分规则是:

  • 会显示在 UI 上的派生值,优先考虑 computed
  • 会触发行为、任务、日志、同步动作的,优先考虑 effect

响应式和 runtime 的边界

这一页最重要的不是 API 名字,而是边界:

  • 响应式系统决定 谁是 dirty 的
  • runtime 决定 dirty 之后怎么局部更新终端

这也是为什么 Ansiq 的 reactive graph 和 UI tree 必须保持分层。

为什么这种模型适合终端应用

终端应用里最常见的变化往往都很局部:

  • transcript 追加一段
  • 某个 footer 数值变化
  • 某个列表选中项变化
  • 某个输入框多了一个字符

这些变化都不值得整树重建。细粒度响应式的价值就在这里:它帮 runtime 先把影响范围缩小。

如果你愿意把这个例子再往前推一步,可以先自己问一个问题:

  • 当前 draft 变了,真的有必要让整个 app 都重新组织吗?

Ansiq 给出的答案就是:不必。先把 dirty scope 缩小,再把渲染范围缩小。

下一步

如果你想先理解机制,继续阅读 响应式系统原理
如果你想继续往“怎么写界面”推进,再读 组件与 view!

Last updated on