响应式图与 UI 树
Ansiq 里有两棵不同的“图”:
- 响应式依赖图
- UI 树
这是一个很容易被误解,但又非常关键的设计点。
上一页已经解释了 reactive graph 本身:
signalcomputedeffect- dirty scope 是怎么传播出来的
所以这一页不再重复那部分,而只关注另一个问题:
当 runtime 已经知道“谁脏了”之后,为什么还需要一棵独立的 UI 树?
UI 树负责什么
UI 树负责:
- 当前有哪些节点
- 哪个节点在哪个位置
- 哪棵子树需要替换
- 布局怎么重新计算
- 哪些终端区域需要重画
它回答的是另外一个问题:
脏了之后,该怎么更新屏幕?
换句话说:
- 响应式图回答“谁脏了”
- UI 树回答“怎么变”
为什么要坚持两阶段模型
把 Ansiq 的更新过程压到最简单,就是两阶段:
第一阶段:响应式传播
回答:
- 哪个 signal 变了
- 哪些 scope 因此 dirty
第二阶段:runtime 更新
回答:
- 哪棵子树 rerender
- 哪些祖先 relayout
- 哪些区域 redraw
- 最终发什么 terminal patch
如果没有这条边界,系统很快就会变成一团:
- 依赖关系层偷偷决定布局
- widget 层偷偷决定 patch
- runtime 不再是统一协调者
为什么不能把它们混起来
如果把响应式图和 UI 树混成一层,你很快会碰到这些问题:
- debug 时看不清到底是依赖关系错了,还是布局错了
- runtime 逻辑和组件逻辑互相污染
- 很难对局部更新做清晰优化
所以 Ansiq 当前一直在刻意守这条边界。
一个简单的心智模型
你可以把它压成一条链:
signal/computed change
-> dependency graph marks scopes dirty
-> runtime collects dirty scopes
-> subtree rerender
-> partial relayout
-> terminal patch为什么这会让实现更稳
这种拆分让很多 runtime 优化都变得更自然:
- dirty queue 化
- subtree replacement
- continuity contract
- damage model
因为你不会试图让“依赖关系层”直接处理“终端 patch”,也不会让 UI 树反过来承担依赖传播。
为什么这对调试也很重要
这条边界不只是为了架构整洁,也直接决定 debug 体验。
当你遇到 bug 时,现在至少可以先问:
- 是 signal/computed/effect 的依赖传播错了?
- 还是 dirty scope 收集错了?
- 还是 subtree replacement / relayout / redraw 出了问题?
如果响应式图和 UI 树深度混在一起,这三个问题会很快搅成一团。
这也是为什么 Ansiq 会不断重复同一句话:
- reactive graph 是一层
- UI tree 是另一层
- runtime loop 是它们之间的协调层
下一步
继续阅读:
Last updated on