一、背景
在我的低代码 Runtime 设计中,一个核心问题是:当数据发生变化(ctx.set(A,newValue))时,如何驱动 UI 更新?
本质问题是在解决:数据变化如何传播出去,并最终驱动 UI 更新
二、问题抽象
从架构角度看,这其实是一个:变化传播模型
常见的思路有:
- 依赖驱动(如 Vue)
- 调度驱动(本文方案)
本文关注的是:中心化调度模型
三、核心设计
1. 事件驱动
它解决的是:变化 → 事件
数据变化不直接操作 UI,而是转化为事件:
ctx.set('state.count', 4)
↓
notify()
↓
notifyUpward()
本质:变化先被“广播”出去
2. 中心化调度
它解决的是:事件汇聚
所有变化最终汇聚到统一入口:rootContext(运行态的根数据节点上下文,调度中心,下文简写rootCtx)
- 所有节点的 ctx 的更新都会向上冒泡
- rootCtx 作为统一调度节点
本质:所有更新都有唯一入口
3. 渲染触发:统一执行
RendererRoot 订阅 rootCtx
当 rootCtx 收到变化:
→ 触发 Renderer 层
→ UI 更新
四、完整执行链路
ctx.set
↓
事件传播(notify / 冒泡)
↓
rootCtx(统一汇聚)
↓
RendererRoot(订阅者)
↓
Renderer 层
↓
UI 更新
示例代码:
// 订阅者
type Listener = () => void
// 运行态的能力上下文
class RuntimeContext {
parent?: RuntimeContext
private listeners = new Set<Listener>()
state: Record<string, any> = {}
subscribe(fn: Listener) {
this.listeners.add(fn)
return () => this.listeners.delete(fn)
}
notify() {
this.listeners.forEach(fn => fn())
}
// 向上冒泡,直到 rootCtx
notifyUpward() {
this.notify()
this.parent?.notifyUpward()
}
set(path: string, value: any) {
this.state[path] = value
this.notifyUpward()
}
}
// 运行态控制中心
class Runtime {
private rootCtx = new RuntimeContext()
getRootContext() {
return this.rootCtx
}
}
// 渲染根节点(渲染器部分)
function RendererRoot({ runtime }: { runtime: Runtime }) {
const [tick, setTick] = useState(0)
useEffect(() => {
const rootCtx = runtime.getRootContext()
return rootCtx.subscribe(() => {
setTick(t => t + 1) // 触发整树 rerender
})
}, [runtime])
return <Renderer />
}
// 某个运行时的交互逻辑中
const rootCtx = runtime.getRootContext()
rootCtx.set('count', 2)
五、设计本质
这套方案可以抽象为四层:
- 数据层:负责“发生变化”
- 事件层:负责“传播变化”
- 调度层:负责“组织更新”
- 视图层:负责“消费结果”
一句话总结:由运行时统一接管 变化 驱动 UI 的过程
六、与 Vue 响应式的区别
| 维度 | 本方案 | Vue |
|---|---|---|
| 模型 | 中心化调度 | 依赖驱动 |
| 更新方式 | 全局调度 | 精确触发 |
| 依赖关系 | 不显式记录 | 自动收集 |
| 性能特点 | 简单场景够用,复杂场景需优化 | 精确更新,性能好 |
| 调试难度 | 容易(单一入口) | 中(依赖追踪链) |
本质区别:
- Vue:数据决定谁更新
- 本方案:系统决定什么时候更新
七、适用场景
1. 低代码
Schema 驱动 UI,模块复杂,需要统一调度
2. 表单引擎
字段联动:比如下拉选项 改变 → 控制 输入框 显示/隐藏
3. 可视化编辑器
拖拽 / 选中 / 属性修改 → 触发整体更新
4. 插件系统 / IDE
多个模块同时影响 UI,需要统一控制更新节奏
八、总结
本方案是一种“调度驱动”的响应式模型,通过中心化调度统一控制 UI 更新。
它的价值不在于“更快”,而在于:更可控、更易扩展、更适合复杂系统
九、思考
在实际架构中,最优解往往是:中心化调度 + 依赖驱动(混合模型),这也是前端响应式系统的演进方向。
所以我这套方案的演进路径:
- v1:整树刷新(当前最小可行)
- v2:依赖追踪 & 局部更新(建立 data → UI 关系,实现局部更新)
- v3:调度优化(批处理 / 优先级)
核心方向:从“调度驱动” → “调度 + 依赖驱动”