Skip to Content
内部原理响应式系统原理

响应式系统原理

上一页已经讲了怎么使用 signalcomputedeffect
这一页讲的是另一个问题:

Ansiq 为什么要这样设计?

先给一个总原则

Ansiq 当前坚持一条边界:

  • 响应式系统只负责“谁脏了”
  • runtime 负责“脏了之后怎么更新屏幕”

如果把这条边界打破,系统很快就会变成:

  • 依赖传播和 UI 更新混在一起
  • debug 时分不清是依赖图错了还是布局错了
  • 局部更新很难继续优化

reactive graph 到底记录什么

可以把 Ansiq 的响应式图理解成三类节点:

  • signal
  • computed
  • effect

signal

signal 代表基础状态源:

  • 当前值是什么
  • 哪些依赖在读取它

computed

computed 代表派生值:

  • 自己不存一份业务状态
  • 由其他响应式值推导出来

effect

effect 代表副作用边界:

  • 它不是用来产生 UI 的
  • 它也不是另一个状态容器
  • 它是在响应式读取变化后触发行为

这里有一个容易混淆的点:

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

一次响应式更新到底怎么走

当你调用 signal.set(...)signal.set_if_changed(...) 时,Ansiq 不是直接重画屏幕,而是先做依赖传播:

Reactive graph 到 runtime 的主链
1signal value changes
2reactive graph marks dependent scopes dirty
3runtime collects dirty scopes
4runtime rerenders a subtree or the root
5layout and patch stages update the terminal

这条链最重要的地方在于:
响应式图到 dirty scope 就停下来了。

它不会继续决定:

  • 哪些祖先 relayout
  • 哪些区域 redraw
  • 终端该发什么 patch

这些都是 runtime 的职责。

为什么不做“整组件重跑中心”的模型

如果每次状态变化都直接默认成“整组件 rerender”,那么:

  • 小输入也会扩大成大更新
  • runtime 很难继续做 subtree replacement
  • damage model 会更粗

Ansiq 想要的是更细的主链:

signal -> dirty scope -> subtree replacement -> partial relayout -> patch

这也是它更接近 Vue / Solid,而不是 React setState -> rerender everything 的地方。

effect 在这套模型里的正确位置

effect 的职责很窄,但很关键:

  • 响应依赖变化
  • 触发副作用
  • 不直接承担 UI 计算

适合它的事情:

  • 日志
  • 同步非 UI 状态
  • 任务协调
  • 触发额外消息

不适合它的事情:

  • 替代 computed
  • 替代应用状态
  • 把所有业务逻辑都塞进去

一个很实用的规则是:

  • 显示值,用 computed
  • 事实状态,用 signal
  • 行为与副作用,用 effect

为什么这对 debug 特别重要

一旦边界清楚,调试时你至少可以先问:

  • 是依赖传播错了吗?
  • 还是 dirty scope 收集错了?
  • 还是 rerender / relayout / patch 某一层错了?

如果响应式图和 UI 树混成一层,这三个问题会很快搅成一团。

什么时候继续深入

如果你想理解更完整的系统设计,继续读:

Last updated on