响应式系统原理
上一页已经讲了怎么使用 signal、computed 和 effect。
这一页讲的是另一个问题:
Ansiq 为什么要这样设计?
先给一个总原则
Ansiq 当前坚持一条边界:
- 响应式系统只负责“谁脏了”
- runtime 负责“脏了之后怎么更新屏幕”
如果把这条边界打破,系统很快就会变成:
- 依赖传播和 UI 更新混在一起
- debug 时分不清是依赖图错了还是布局错了
- 局部更新很难继续优化
reactive graph 到底记录什么
可以把 Ansiq 的响应式图理解成三类节点:
signalcomputedeffect
signal
signal 代表基础状态源:
- 当前值是什么
- 哪些依赖在读取它
computed
computed 代表派生值:
- 自己不存一份业务状态
- 由其他响应式值推导出来
effect
effect 代表副作用边界:
- 它不是用来产生 UI 的
- 它也不是另一个状态容器
- 它是在响应式读取变化后触发行为
这里有一个容易混淆的点:
effect不同于 React 的useEffect。
它没有依赖数组,依赖由执行时的读取自动追踪。
一次响应式更新到底怎么走
当你调用 signal.set(...) 或 signal.set_if_changed(...) 时,Ansiq 不是直接重画屏幕,而是先做依赖传播:
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